diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..a78365a --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# File generated by pre-commit: https://pre-commit.com +# ID: 138fd403232d2ddd5efb44317e38bf03 + +# start templated +INSTALL_PYTHON=/usr/bin/python3 +ARGS=(hook-impl --config=.pre-commit-config.yaml --hook-type=pre-commit) +# end templated + +HERE="$(cd "$(dirname "$0")" && pwd)" +ARGS+=(--hook-dir "$HERE" -- "$@") + +if [ -x "$INSTALL_PYTHON" ]; then + exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}" +elif command -v pre-commit > /dev/null; then + exec pre-commit "${ARGS[@]}" +else + echo '`pre-commit` not found. Did you forget to activate your virtualenv?' 1>&2 + exit 1 +fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..7dfe78d --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,10 @@ +[mypy] +warn_unused_configs = True +check_untyped_defs=True + +[mypy-gi.*] +ignore_missing_imports = True +[mypy-dbus.*] +ignore_missing_imports = True +[mypy-dbusmock.*] +ignore_missing_imports = True \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b8796dc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.7.0 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format + args: [ --check ] +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.14.1 + hooks: + - id: mypy + additional_dependencies: + - pytest \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f1ba24..35cf777 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,64 +1,4 @@ # XDG Desktop Portal - Contributing Guide -Before developing features or fixing bugs, please make sure you are have done -the following: - -- Your code is not on the *main* branch of your fork -- The code has been tested -- All commit messages are properly formatted and commits squashed where - appropriate -- You have included updates to all appropriate documentation - -We use GitHub pull requests to review contributions. Please be kind and patient -as code reviews can be long and minutious. - -## Development - -xdg-desktop-portal usually runs as a user session service, initialized on -demand through D-Bus activation. It usually starts with the session though, -as many desktop environments try to talk to xdg-desktop-portal on startup. -xdg-desktop-portal initializes specific backends through D-Bus activation -as well. - -### Building - -To build xdg-desktop-portal, first make sure you have the build dependencies -installed through your distribution's package manager. With them installed, -run: - -``` -$ meson setup . _build -$ meson compile -C _build -``` - -Some distributions install portal configuration files in `/usr`, while Meson -defaults to the prefix `/usr/local`. If the portal configuration files in your -distribution are in `/usr/share/xdg-desktop-portal/portals`, re-configure -Meson using `meson setup --reconfigure . _build --prefix /usr` and compile -again. - -### Running - -xdg-desktop-portal needs to own the D-Bus name and replace the user session -service that might already be running. To do so, run: - -``` -$ _build/src/xdg-desktop-portal --replace -``` - -You may need to restart backends after replacing xdg-desktop-portal (please -replace `[name]` with the backend name, e.g. `gnome` or `kde` or `wlr`): - -``` -$ systemctl --user restart xdg-desktop-portal-[name].service -``` - -### Testing - -To execute the test suite present in xdg-desktop-portal, make sure you built it -with `-Dlibportal=enabled`, and run: - -``` -$ meson test -C _build -``` - +The contribution guidelines have been moved to the online documentation: +https://flatpak.github.io/xdg-desktop-portal/docs/for-contributors.html diff --git a/NEWS b/NEWS.md similarity index 70% rename from NEWS rename to NEWS.md index ed7f7c5..c5c47d8 100644 --- a/NEWS +++ b/NEWS.md @@ -1,43 +1,199 @@ -Changes in 1.18.4 +Changes in 1.20.3 ================= -Released: 2024-04-18 +Released: 2025-05-20 -- Don't allow commandline arrays when the first commandline item starts with - whitespace or hyphen. (CVE-2024-32462) -- Do not store device access permission if it returned an error. -- Fix crash with config files without a default backend set. +Bug Fixes: -Changes in 1.18.3 +- Add a fallback code path for GLib older than 2.76 (#1728) +- Don't require a .desktop file for Flatpak and Snap apps (#1729) + +Changes in 1.20.2 +================= +Released: 2025-05-19 + +Bug Fixes: + +- Fix a crash when loading information from Flatpak apps +- Fix fd handling to prevent EBADF errors + +Changes in 1.20.1 +================= +Released: 2025-05-15 + +Enhancements: + +- Code cleanups and improvements to app info tracking +- Include PID/TID in realtime portal error messages +- Search for portal backends in $XDG_DATA_DIRS (#603) +- Prioritize user portal configs over system ones + +Bug Fixes: + +- Fix race condition in the host registry portal +- Avoid spurious warnings when dbus.service stops +- Documentation fixes (#1663) +- Fix running tests from /tmp +- Fix installing dynamic launcher (#1674) +- Improve error reporting in the document portal +- Fix incorrect state tracking in input capture portal + +Changes in 1.20.0 +================= +Released: 2025-02-19 + +Enhancements: + +- Document how the test suite works. +- Improve the test runner script. + +Changes in 1.19.4 +================= +Released: 2025-02-15 + +New Features: + +- Introduce the host app registry. This interface allows host system apps + (i.e. apps not running under a sandboxing mechanism like Flatpak) register + themselves with XDG Desktop Portal. This allows XDG Desktop Portal to use + a proper app id, and desktop file, improving the interaction with portal + backends. + +Enhancements: + +- Use a new internal script to simplify running tests. + +Bug Fixes: + +- Properly escape notification body in the Notification portal. +- Fix various documentation links in the USB portal documentation page. + +Changes in 1.19.3 ================= -Released: 2024-04-04 +Released: 2025-02-12 + +Bug Fixes: + +- Fix documentation links in the USB portal page. +- Make the Document portal track open files, and release them when shutting + down. This should fix some harmless leak reports. +- Fix a memory leak, a crash, and improve robustness against non-existing + folders in the Dynamic Launcher portal. +- Fix build with PipeWire 1.3.82 + +Enhancements: + +- Make the host path xattr more useful by removing the trailing end character, + and also reporting the xattr of files inside folders added to the document + store. +- Remove libportal-based integrated tests. This should remove the cyclic + dependency between libportal, and xdg-desktop-portal. All tests are now based + on the Python testing framework. + +Changes in 1.19.2 +================= +Released: 2025-01-20 + +Bug Fixes: + +- Fix permission check for host system apps in the Camera portal. +- Do not expose the Settings portal if there are no backends available. +- Disable sounds-related notification tests if the project is built without + wavparse. + +Enhancements: + +- Start porting the test suite to Python tests. Once finished, this should + break the cyclic dependency between xdg-desktop-portal and libportal. +- Install Python-based tests. This is mostly useful for distributions to run + tests as part of their packaging process. + +Changes in 1.19.1 +================= +Released: 2024-12-21 + +Dependencies: + +- XDG Desktop Portal now requires GLib 2.72 or higher. + +New Features: + +- Introduce the Notification v2 portal. This updated version of the Notification + portal supports a plethora of new fields for notifications, such as sounds, + categories, purpose, and more. +- Introduce the USB portal. This portal allows apps with relevant permissions + to enumerate and acquire access to specific USB devices. +- Introduce a new `SchemeSupported` method to the OpenURI portal. This new + method allows apps to know ahead of time if the host system is able to deal + with a particular scheme. + +Enhancements: + +- Continued the move towards Python-based tests. This should simplify the + test setup in the project quite significantly, and also will allow removing + the cyclic dependency between libportal and XDG Desktop Portal. +- Introduce umockdev-based tests. +- Improve the icon validator so it can deal with memfd-based icons. +- Clarify behavior of the Settings portal for non-standardized keys. +- In the Global Shortcuts portal, clarify that the result the `BindShortcuts` + of may be a subset of all requested shortcuts. +- Add a documentation page about icon validation requirements. + +Bug Fixes: -- Don't try to read D-Bus object properties of Request objects on construction. -- Fix various memory and file descriptor leaks. -- Minuscule optimization to the ScreenCast portal so that it stores restoration - data with a single D-Bus call, instead of two. -- Fix a crash in the OpenURI file when trying to open a non-existing file. -- Various smaller bug fixes. +- Fix memory leaks in the Background, Email, and Global Shortcuts portals. +- Fix a general file descriptor & memory leak. +- Fix a regression in the Settings portal. +- New and updated translations. -Changes in 1.18.2 +Changes in 1.19.0 ================= -Released: 2023-11-22 - -- Pass the token to the OpenURI portal and, when missing, an empty string. -- Fix various memory and file descriptor leaks in the Document portal. -- Make files and folders openend with the Document portal close properly. This - should fix cases where the Document portal prevented external devices from - unmounting, due to files inside them not getting closed after applications - stop using them. -- Implement FUSE getlk and setlk callbacks.This should enable using sqlite3 - through the Document portal. -- Properly resolve fd symlinks before opening them with O_NOFOLLOW. -- Fix cases where the portal id is assumed to match the .desktop file name. -- Allow sending directories in the file transfer portal. This should make it - possible to, among other things, drag and drop folders and files simultaneously - from and to sandboxed applications. -- Fallback to a hardcoded check to xdg-desktop-portal-gtk in the absence of any - other portal or configuration file, as a last resort mechanism. -- Various smaller fixes to the build system. +Released: 2024-10-09 + +- Completely rework and restructure the documentation website. Documentation is + now segmented by target audience (app developers, desktop developers, and + contributors). It also documents how the Document portal operates, the FUSE + filesystem, and custom file attributes. This is available in the following + address: https://flatpak.github.io/xdg-desktop-portal/docs/index.html +- The portals.conf parser is now able to handle fallback backends better, and + respects the order of backends in the config file. +- Try to use the xdg-desktop-portal-gtk backend as a last resort backend, if + everything else fails. +- Implement getlk and setlk, and honour O_NOFOLLOW, in the Document portal's + FUSE filesystem. +- Neutralize the Devices portal. Originally the Devices portal was introduced + so that services like PulseAudio or PipeWire could request access to + microphones and cameras on the behalf of apps. It was not meant to be used by + sandboxed apps directly, which is unusual for a portal. Practically, however, + it didn't take off. +- Implement PID/TID mapping for host system apps. +- Add a new "supported_output_file_formats" option to the Print portal. This + can be used by apps like browsers to limit the output file formats presented + by the Print portal backend. For example, an app can limit file printing to + PDF files. +- Add a new "GetHostPaths" method to the Document portal, which allows mapping + file descriptors to paths on the host system. This can be used by apps to + show more meaningful file paths in the user interface. +- Like the new method above, the Document portal sets the + "user.document-portal.host-path" xattr on files, pointing to the the host + system file path. The use case is similar to "GetHostPaths". +- Make the Background portal more robust when validating autostart files. +- Clarify behavior of the File Chooser portal in the documentation pages. +- Improve robustness against deleted o_path fds in the Document portal. +- Fix a warning in some systems while trying to load Request D-Bus object + properties. +- Fix a physical inode leak in the Document portal. +- Various improvements to the test suite. Python-based tests now run in parallel + and are more careful when setting up the mock D-Bus server. Tests also start + dbus-monitor if necessary now. FUSE tests of the Document portal have been + made more TAP-alike now. +- Memory leak fixes in a variety of portals and services, including the + permissions database, the Document portal, the File Transfer portal, the + Location portal, the Background portal, tests, and the icon validator. And + more. There's a lot of memory leak fixes everywhere, really. +- Major refactorings of the icon validator. Icons are now limited to 4MB files. +- Update XML specification specifying session handle type to match current + actual ABI in GlobalShortcuts, Inhibit, RemoteDesktop, and ScreenCast portals. +- New and updated translations. Changes in 1.18.1 ================= diff --git a/README.md b/README.md index b77c553..71b0c85 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,19 @@ -![Portals](doc/website/assets/readme.png) +[![Portals](doc/website/assets/readme.png)](https://flatpak.github.io/xdg-desktop-portal/) -# xdg-desktop-portal +# [XDG Desktop Portal](https://flatpak.github.io/xdg-desktop-portal/) -A portal frontend service for [Flatpak](http://www.flatpak.org) and possibly -other desktop containment frameworks. +A portal frontend service for [Flatpak](https://flatpak.org) and other +desktop containment frameworks. xdg-desktop-portal works by exposing a series of D-Bus interfaces known as -_portals_ under a well-known name (org.freedesktop.portal.Desktop) and object -path (/org/freedesktop/portal/desktop). +_portals_ under a well-known name (`org.freedesktop.portal.Desktop`) and object +path (`/org/freedesktop/portal/desktop`). The portal interfaces include APIs for file access, opening URIs, printing and others. -Documentation for the available D-Bus interfaces can be found +Documentation about the Common Conventions, as well as documentation for +App Developers, Desktop Developers and Contributors can be found [here](https://flatpak.github.io/xdg-desktop-portal/docs/). -## Version numbering -xdg-desktop-portal uses even minor vesion numbers for stable versions, and odd -minor version numbers for unstable versions. During an unstable version cycle, -portal APIs can make backward incompatible changes, meaning that applications -should only depend on APIs defined in stable xdg-desktop-portal versions in -production. - -## Building xdg-desktop-portal - -xdg-desktop-portal depends on GLib and Flatpak. -To build the documentation, you will need xmlto and the docbook stylesheets. -For more instructions, please read [CONTRIBUTING.md][contributing]. - -## Using portals - -Flatpak grants sandboxed applications _talk_ access to names in the -org.freedesktop.portal.\* prefix. One possible way to use the portal APIs -is thus just to make D-Bus calls. For many of the portals, toolkits (e.g. -GTK+) are expected to support portals transparently if you use suitable -high-level APIs. - -To implement most portals, xdg-desktop-portal relies on a backend -that provides implementations of the org.freedesktop.impl.portal.\* interfaces. - -Here are some examples of available backends: - -- GTK [xdg-desktop-portal-gtk](http://github.com/flatpak/xdg-desktop-portal-gtk) -- GNOME [xdg-desktop-portal-gnome](https://gitlab.gnome.org/GNOME/xdg-desktop-portal-gnome/) -- KDE [xdg-desktop-portal-kde](https://invent.kde.org/plasma/xdg-desktop-portal-kde) -- LXQt [xdg-desktop-portal-lxqt](https://github.com/lxqt/xdg-desktop-portal-lxqt) -- Pantheon (elementary OS) [xdg-desktop-portal-pantheon](https://github.com/elementary/portals) -- wlroots [xdg-desktop-portal-wlr](https://github.com/emersion/xdg-desktop-portal-wlr) -- Deepin [xdg-desktop-portal-dde](https://github.com/linuxdeepin/xdg-desktop-portal-dde) -- Xapp (Cinnamon, MATE, Xfce) [xdg-desktop-portal-xapp](https://github.com/linuxmint/xdg-desktop-portal-xapp) - -## Design considerations - -There are several reasons for the frontend/backend separation of the portal -code: -- We want to have _native_ portal dialogs that match the session desktop (i.e. - GTK+ dialogs for GNOME, Qt dialogs for KDE) -- One of the limitations of the D-Bus proxying in flatpak is that allowing a - sandboxed app to talk to a name implicitly also allows it to talk to any other - name owned by the same unique name. Therefore, sandbox-facing D-Bus apis - should generally be hosted on a dedicated bus connection. For portals, the - frontend takes care of this for us. -- The frontend can handle all the interaction with _portal infrastructure_, such - as the permission store and the document store, freeing the backends to focus - on just providing a user interface. -- The frontend can also handle argument validation, and be strict about only - letting valid requests through to the backend. - -The portal apis are all following the pattern of an initial method call, whose -response returns an object handle for an _org.freedesktop.portal.Request_ object -that represents the portal interaction. The end of the interaction is done via a -_Response_ signal that gets emitted on that object. This pattern was chosen over -a simple method call with return, since portal apis are expected to show dialogs -and interact with the user, which may well take longer than the maximum method -call timeout of D-Bus. Another advantage is that the caller can cancel an -ongoing interaction by calling the _Cancel_ method on the request object. - -One consideration for deciding the shape of portal APIs is that we want them to -'hide' behind existing library APIs where possible, to make it as easy as -possible to have apps use them _transparently_. For example, the OpenFile portal -is working well as a backend for the GtkFileChooserNative API. - -When it comes to files, we need to be careful to not let portal apis subvert the -limited filesystem view that apps have in their sandbox. Therefore, files should -only be passed into portal APIs in one of two forms: -- As a document ID referring to a file that has been exported in the document - portal -- As an open fd. The portal can work its way back to a file path from the fd, - and passing an fd proves that the app inside the sandbox has access to the - file to open it. - -When it comes to processes, passing pids around is not useful in a sandboxed -world where apps are likely in their own pid namespace. And passing pids from -inside the sandbox is problematic, since the app can just lie. - - -[contributing]: https://github.com/flatpak/xdg-desktop-portal/blob/main/CONTRIBUTING.md diff --git a/RELEASE_HOWTO.md b/RELEASE_HOWTO.md index 167baca..5a53692 100644 --- a/RELEASE_HOWTO.md +++ b/RELEASE_HOWTO.md @@ -1,21 +1,60 @@ # Steps for doing a xdg-desktop-portal release - - git clean -fxd - - meson setup . _build && meson compile -C _build/ xdg-desktop-portal-update-po - - git add po/*po && git commit -m "Update po files" - - git clean -fxd - - add content to NEWS - - git commit -m - - git push origin main - - meson setup . _build -Ddocbook-docs=enabled - - meson dist -C _build - - git tag - - git push origin refs/tags/ - - upload tarball to github as release - - edit release, copy NEWS section in - - update portal api docs in the gh-pages branch - - bump version in meson.build - - git commit -m "Post-release version bump" - - git push origin main - - Update SECURITY.md if this is a new stable release - - Update .github/ISSUE_TEMPLATE/bug-report.yml if this is a new stable release +### Prepare the release + +- Make sure the version in `meson.build` is correct +- Create a branch +```sh +$ git checkout -b release-${version} +``` +- Update translations +```sh +$ ninja -C ${builddir} xdg-desktop-portal-update-po +$ git add po/ +$ git commit -m "Update translations" +``` +- Add your changelog to the `NEWS.md` file +```sh +$ git add NEWS.md +$ git commit -m ${version} +``` +- Push your branch +```sh +$ git push -u ${fork} release-${version} +``` +- Open a pull request + +### Create a Signed Tag + +**NOTE**: Only project maintainers can create a tag. + +Make sure that: + - You're on the `main` branch, or a stable branch; + - The tip of the branch is a release commit (e.g. `1.19.4`) + - The version in `meson.build` is correct + +Then create the tag: + +```sh +$ git evtag sign ${version} +$ git push -u origin ${version} +``` + +### Post-Release + +- Update version number in `meson.build` to the next release version +- Start a new section in `NEWS.md` +```md +Changes in ${nextVersion} +================= +Released: Not yet +... +``` + +### Post-Branching + +After creating a stable branch: + +- Update version number in `meson.build` to the next unstable release version +- Update `SECURITY.md` +- Update `.github/ISSUE_TEMPLATE/bug-report.yml` diff --git a/SECURITY.md b/SECURITY.md index 58395ef..bc5f66f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,7 +10,7 @@ please check | Version | Supported | Status | -------- | ------------------ | ------------------------------------------------------------- | -| 1.19.x | :white_check_mark: | Development branch, releases may include non-security changes | -| 1.18.x | :white_check_mark: | Stable branch, recommended for use in distributions | -| 1.16.x | :white_check_mark: | Old stable branch, still maintained | -| <= 1.14.x | :x: | Older branches, no longer supported | +| 1.21.x | :white_check_mark: | Development branch, releases may include non-security changes | +| 1.20.x | :white_check_mark: | Stable branch, recommended for use in distributions | +| 1.18.x | :white_check_mark: | Old stable branch, still maintained | +| <= 1.16.x | :x: | Older branches, no longer supported | diff --git a/data/meson.build b/data/meson.build index eea49ad..9f2eaf1 100644 --- a/data/meson.build +++ b/data/meson.build @@ -9,7 +9,6 @@ portal_sources = files( 'org.freedesktop.portal.Background.xml', 'org.freedesktop.portal.Camera.xml', 'org.freedesktop.portal.Clipboard.xml', - 'org.freedesktop.portal.Device.xml', 'org.freedesktop.portal.Documents.xml', 'org.freedesktop.portal.DynamicLauncher.xml', 'org.freedesktop.portal.Email.xml', @@ -36,9 +35,14 @@ portal_sources = files( 'org.freedesktop.portal.Session.xml', 'org.freedesktop.portal.Settings.xml', 'org.freedesktop.portal.Trash.xml', + 'org.freedesktop.portal.Usb.xml', 'org.freedesktop.portal.Wallpaper.xml', ) +portal_host_sources = files( + 'org.freedesktop.host.portal.Registry.xml', +) + portal_impl_sources = files( 'org.freedesktop.impl.portal.Access.xml', 'org.freedesktop.impl.portal.Account.xml', @@ -62,6 +66,7 @@ portal_impl_sources = files( 'org.freedesktop.impl.portal.Secret.xml', 'org.freedesktop.impl.portal.Session.xml', 'org.freedesktop.impl.portal.Settings.xml', + 'org.freedesktop.impl.portal.Usb.xml', 'org.freedesktop.impl.portal.Wallpaper.xml', ) @@ -69,6 +74,6 @@ background_monitor_sources = files( 'org.freedesktop.background.Monitor.xml', ) -install_data([portal_sources, portal_impl_sources], +install_data([portal_sources, portal_host_sources, portal_impl_sources], install_dir: datadir / 'dbus-1' / 'interfaces', ) diff --git a/data/org.freedesktop.background.Monitor.xml b/data/org.freedesktop.background.Monitor.xml index 1a2028a..6a08457 100644 --- a/data/org.freedesktop.background.Monitor.xml +++ b/data/org.freedesktop.background.Monitor.xml @@ -2,10 +2,12 @@ - + + + diff --git a/data/org.freedesktop.host.portal.Registry.xml b/data/org.freedesktop.host.portal.Registry.xml new file mode 100644 index 0000000..7fcd396 --- /dev/null +++ b/data/org.freedesktop.host.portal.Registry.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + diff --git a/data/org.freedesktop.impl.portal.Access.xml b/data/org.freedesktop.impl.portal.Access.xml index e20f846..6959980 100644 --- a/data/org.freedesktop.impl.portal.Access.xml +++ b/data/org.freedesktop.impl.portal.Access.xml @@ -2,10 +2,12 @@ @@ -87,8 +81,10 @@ + + diff --git a/data/org.freedesktop.impl.portal.Account.xml b/data/org.freedesktop.impl.portal.Account.xml index a6acb33..7d2d99a 100644 --- a/data/org.freedesktop.impl.portal.Account.xml +++ b/data/org.freedesktop.impl.portal.Account.xml @@ -2,10 +2,12 @@ + + diff --git a/data/org.freedesktop.impl.portal.AppChooser.xml b/data/org.freedesktop.impl.portal.AppChooser.xml index cbb42ee..18eff7e 100644 --- a/data/org.freedesktop.impl.portal.AppChooser.xml +++ b/data/org.freedesktop.impl.portal.AppChooser.xml @@ -2,10 +2,12 @@ diff --git a/data/org.freedesktop.impl.portal.Background.xml b/data/org.freedesktop.impl.portal.Background.xml index 509c516..6ccfea6 100644 --- a/data/org.freedesktop.impl.portal.Background.xml +++ b/data/org.freedesktop.impl.portal.Background.xml @@ -2,10 +2,12 @@ - + + - + + @@ -87,14 +85,16 @@ @flags: Flags influencing the details of the autostarting @result: TRUE if autostart was enabled, FALSE otherwise + This is deprecated! New versions of xdg-desktop-portal will not + call this method. + Enables or disables autostart for an application. The following flags are understood: - - 1: Use D-Bus activation - + + - ``1``: Use D-Bus activation --> - + @@ -108,7 +108,7 @@ This signal gets emitted when applications change their state and it is worth calling GetAppState again. --> - + diff --git a/data/org.freedesktop.impl.portal.Clipboard.xml b/data/org.freedesktop.impl.portal.Clipboard.xml index 66e5dcb..ff22271 100644 --- a/data/org.freedesktop.impl.portal.Clipboard.xml +++ b/data/org.freedesktop.impl.portal.Clipboard.xml @@ -2,10 +2,12 @@ + + + @@ -103,6 +87,7 @@ + @@ -112,15 +97,14 @@ RequestInstallToken: @app_id: App id of the application @options: Vardict with optional further information - @response: 0 if the caller should be allowed to do non-interactive - launcher installation, a nonzero number otherwise + @response: 0 if the caller should be allowed to do non-interactive launcher installation, a nonzero number otherwise The @response returned by this method is used to determine whether to give an install token to the caller, which can be used to avoid the need for a confirmation dialog; the token can be passed to the - org.freedesktop.portal.DynamicLauncher.Install() method just as if it + :ref:`org.freedesktop.portal.DynamicLauncher.Install` method just as if it were acquired via the - org.freedesktop.portal.DynamicLauncher.PrepareInstall() method. + :ref:`org.freedesktop.portal.DynamicLauncher.PrepareInstall` method. It is up to the portal implementation to decide whether this method is allowed based on the @app_id of the caller. @@ -129,6 +113,7 @@ --> + @@ -137,10 +122,8 @@ A bitmask of available launcher types. Currently defined types are: - - 1: Application. A launcher that represents an application. - 2: Webapp. A launcher that represents a web app. - + - ``1``: Application. A launcher that represents an application. + - ``2``: Webapp. A launcher that represents a web app. --> diff --git a/data/org.freedesktop.impl.portal.Email.xml b/data/org.freedesktop.impl.portal.Email.xml index d958798..a9ee13a 100644 --- a/data/org.freedesktop.impl.portal.Email.xml +++ b/data/org.freedesktop.impl.portal.Email.xml @@ -2,10 +2,12 @@ diff --git a/data/org.freedesktop.impl.portal.FileChooser.xml b/data/org.freedesktop.impl.portal.FileChooser.xml index b3bf981..c93041b 100644 --- a/data/org.freedesktop.impl.portal.FileChooser.xml +++ b/data/org.freedesktop.impl.portal.FileChooser.xml @@ -2,10 +2,12 @@ @@ -138,9 +117,9 @@ @@ -248,10 +198,12 @@ diff --git a/data/org.freedesktop.impl.portal.GlobalShortcuts.xml b/data/org.freedesktop.impl.portal.GlobalShortcuts.xml index fb33ee1..52d3292 100644 --- a/data/org.freedesktop.impl.portal.GlobalShortcuts.xml +++ b/data/org.freedesktop.impl.portal.GlobalShortcuts.xml @@ -2,10 +2,12 @@ - + @@ -63,61 +57,53 @@ + + + - - - + - + diff --git a/data/org.freedesktop.impl.portal.Inhibit.xml b/data/org.freedesktop.impl.portal.Inhibit.xml index 0c20d76..1bb6c13 100644 --- a/data/org.freedesktop.impl.portal.Inhibit.xml +++ b/data/org.freedesktop.impl.portal.Inhibit.xml @@ -2,10 +2,12 @@ @@ -67,8 +66,8 @@ + diff --git a/data/org.freedesktop.impl.portal.InputCapture.xml b/data/org.freedesktop.impl.portal.InputCapture.xml index c431a41..bcaf787 100644 --- a/data/org.freedesktop.impl.portal.InputCapture.xml +++ b/data/org.freedesktop.impl.portal.InputCapture.xml @@ -2,10 +2,12 @@ - + @@ -82,8 +74,8 @@ - + @@ -121,8 +107,8 @@ - + + @@ -178,7 +155,7 @@ - + @@ -254,30 +225,30 @@ - + + + + + @@ -424,11 +385,9 @@ Currently defined types are: - - 1: KEYBOARD - 2: POINTER - 4: TOUCHSCREEN - + - ``1``: KEYBOARD + - ``2``: POINTER + - ``4``: TOUCHSCREEN --> diff --git a/data/org.freedesktop.impl.portal.Lockdown.xml b/data/org.freedesktop.impl.portal.Lockdown.xml index 023e858..0b3dfda 100644 --- a/data/org.freedesktop.impl.portal.Lockdown.xml +++ b/data/org.freedesktop.impl.portal.Lockdown.xml @@ -2,10 +2,12 @@ + @@ -60,16 +69,36 @@ + + + + + @@ -77,5 +106,7 @@ + + diff --git a/data/org.freedesktop.impl.portal.PermissionStore.xml b/data/org.freedesktop.impl.portal.PermissionStore.xml index 36d98ae..6f61144 100644 --- a/data/org.freedesktop.impl.portal.PermissionStore.xml +++ b/data/org.freedesktop.impl.portal.PermissionStore.xml @@ -5,10 +5,12 @@ - + - - - - + + + + + - - - - - + + + + + + - - + + - - - - + + + + - - - - - + + + + + - - - + + + - - - - + + + + - - + + - - - - - + + + + + + diff --git a/data/org.freedesktop.impl.portal.Print.xml b/data/org.freedesktop.impl.portal.Print.xml index 88e6163..c0a4b55 100644 --- a/data/org.freedesktop.impl.portal.Print.xml +++ b/data/org.freedesktop.impl.portal.Print.xml @@ -2,10 +2,12 @@ - - + - + + + + - + - + + - - + - - diff --git a/data/org.freedesktop.impl.portal.RemoteDesktop.xml b/data/org.freedesktop.impl.portal.RemoteDesktop.xml index a8dc551..9f1a3b5 100644 --- a/data/org.freedesktop.impl.portal.RemoteDesktop.xml +++ b/data/org.freedesktop.impl.portal.RemoteDesktop.xml @@ -2,10 +2,12 @@ @@ -63,8 +61,8 @@ @@ -130,6 +112,7 @@ + @@ -137,10 +120,10 @@ - + @@ -207,7 +176,7 @@ + + @@ -244,7 +215,7 @@ + + + + + + @@ -391,7 +360,7 @@ + @@ -416,7 +386,7 @@ + diff --git a/data/org.freedesktop.impl.portal.Request.xml b/data/org.freedesktop.impl.portal.Request.xml index 4402c40..fc474a7 100644 --- a/data/org.freedesktop.impl.portal.Request.xml +++ b/data/org.freedesktop.impl.portal.Request.xml @@ -2,10 +2,12 @@ @@ -59,8 +57,8 @@ @@ -152,10 +129,10 @@ @@ -280,22 +239,19 @@ A bitmask of available source types. Currently defined types are: - - 1: MONITOR - 2: WINDOW - 4: VIRTUAL - + - ``1``: MONITOR: Share existing monitors + - ``2``: WINDOW: Share application windows + - ``4``: VIRTUAL: Extend with new virtual monitor --> diff --git a/data/org.freedesktop.impl.portal.Screenshot.xml b/data/org.freedesktop.impl.portal.Screenshot.xml index 1c27651..265ef25 100644 --- a/data/org.freedesktop.impl.portal.Screenshot.xml +++ b/data/org.freedesktop.impl.portal.Screenshot.xml @@ -2,10 +2,12 @@ @@ -87,9 +78,9 @@ diff --git a/data/org.freedesktop.impl.portal.Secret.xml b/data/org.freedesktop.impl.portal.Secret.xml index f540a72..8709522 100644 --- a/data/org.freedesktop.impl.portal.Secret.xml +++ b/data/org.freedesktop.impl.portal.Secret.xml @@ -2,10 +2,12 @@ diff --git a/data/org.freedesktop.impl.portal.Session.xml b/data/org.freedesktop.impl.portal.Session.xml index a2e7a2a..da74658 100644 --- a/data/org.freedesktop.impl.portal.Session.xml +++ b/data/org.freedesktop.impl.portal.Session.xml @@ -2,10 +2,12 @@ - - - + + + + - - - - + + + + - - - - + + + + diff --git a/data/org.freedesktop.impl.portal.Usb.xml b/data/org.freedesktop.impl.portal.Usb.xml new file mode 100644 index 0000000..37168d8 --- /dev/null +++ b/data/org.freedesktop.impl.portal.Usb.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/org.freedesktop.impl.portal.Wallpaper.xml b/data/org.freedesktop.impl.portal.Wallpaper.xml index c16722a..da210dc 100644 --- a/data/org.freedesktop.impl.portal.Wallpaper.xml +++ b/data/org.freedesktop.impl.portal.Wallpaper.xml @@ -2,10 +2,12 @@ + diff --git a/data/org.freedesktop.portal.Account.xml b/data/org.freedesktop.portal.Account.xml index c8fb98a..86bb76f 100644 --- a/data/org.freedesktop.portal.Account.xml +++ b/data/org.freedesktop.portal.Account.xml @@ -2,10 +2,12 @@ + diff --git a/data/org.freedesktop.portal.Background.xml b/data/org.freedesktop.portal.Background.xml index 8ef85dc..1b83fcd 100644 --- a/data/org.freedesktop.portal.Background.xml +++ b/data/org.freedesktop.portal.Background.xml @@ -2,10 +2,12 @@ + @@ -107,24 +88,21 @@ SetStatus: @options: Vardict with optional further information + This method was added in version 2 of this interface. + Sets the status of the application running in background. Supported keys in the @options vardict include: - - - message s - - A string that will be used as the status message of the application. - This should be a single line that can be presented to the user in a - list, not a full sentence or paragraph. Must be shorter than 96 - characters. - - - - - This method was added in version 2. + + * ``message`` (``s``) + + A string that will be used as the status message of the application. + This should be a single line that can be presented to the user in a + list, not a full sentence or paragraph. Must be shorter than 96 + characters. --> + diff --git a/data/org.freedesktop.portal.Camera.xml b/data/org.freedesktop.portal.Camera.xml index f655265..f0cf1d4 100644 --- a/data/org.freedesktop.portal.Camera.xml +++ b/data/org.freedesktop.portal.Camera.xml @@ -2,10 +2,12 @@ + @@ -59,8 +57,7 @@ Open a file descriptor to the PipeWire remote where the camera nodes are available. The file descriptor should be used to create a - pw_core object, by using - pw_context_connect_fd. + ``pw_core`` object, by using ``pw_context_connect_fd()``. This method will only succeed if the application already has permission to access camera devices. @@ -70,6 +67,7 @@ + diff --git a/data/org.freedesktop.portal.Clipboard.xml b/data/org.freedesktop.portal.Clipboard.xml index 912341c..9a85063 100644 --- a/data/org.freedesktop.portal.Clipboard.xml +++ b/data/org.freedesktop.portal.Clipboard.xml @@ -2,10 +2,12 @@ + + @@ -127,7 +127,7 @@ + - - - - - - - - - - - - - - diff --git a/data/org.freedesktop.portal.Documents.xml b/data/org.freedesktop.portal.Documents.xml index c5be1eb..4e2b264 100644 --- a/data/org.freedesktop.portal.Documents.xml +++ b/data/org.freedesktop.portal.Documents.xml @@ -1,14 +1,14 @@ - + - + - + - - - - + + + + - - - - - + + + + + + - - - - - - + + + + + + + + - - - - - - - + + + + + + + + - - - + + + - - - + + + - + - - + + - - - + + + + - - + + + + + + + + + + diff --git a/data/org.freedesktop.portal.DynamicLauncher.xml b/data/org.freedesktop.portal.DynamicLauncher.xml index a6ce2cb..4f3a6e4 100644 --- a/data/org.freedesktop.portal.DynamicLauncher.xml +++ b/data/org.freedesktop.portal.DynamicLauncher.xml @@ -2,10 +2,12 @@ @@ -47,7 +52,7 @@ + + + diff --git a/data/org.freedesktop.portal.Email.xml b/data/org.freedesktop.portal.Email.xml index 4ff1730..51c78ec 100644 --- a/data/org.freedesktop.portal.Email.xml +++ b/data/org.freedesktop.portal.Email.xml @@ -2,10 +2,12 @@ + diff --git a/data/org.freedesktop.portal.FileChooser.xml b/data/org.freedesktop.portal.FileChooser.xml index 6d167e9..775b30f 100644 --- a/data/org.freedesktop.portal.FileChooser.xml +++ b/data/org.freedesktop.portal.FileChooser.xml @@ -2,10 +2,12 @@ + + + diff --git a/data/org.freedesktop.portal.FileTransfer.xml b/data/org.freedesktop.portal.FileTransfer.xml index 72b6d40..aefc4df 100644 --- a/data/org.freedesktop.portal.FileTransfer.xml +++ b/data/org.freedesktop.portal.FileTransfer.xml @@ -2,10 +2,12 @@ @@ -52,6 +54,7 @@ @key: a key that needs to be passed to org.freedesktop.portal.FileTransfer.RetrieveFiles() to obtain the files Starts a session for a file transfer. + The caller should call org.freedesktop.portal.FileTransfer.AddFiles() at least once, to add files to this session. @@ -60,28 +63,26 @@ it has the @key. Supported keys in the @options vardict include: - - - writable b - - Whether to allow the chosen application to write to the files. - Default: False - - This key only takes effect for files that need to be exported - in the document portal for the receiving app. But it does require - the passed-in file descriptors to be writable. - - - - autostop b - - Whether to stop the transfer automatically after the first - org.freedesktop.portal.FileTransfer.RetrieveFiles() call. Default: True - - - + + * ``writable`` (``b``) + + Whether to allow the chosen application to write to the files. + + This key only takes effect for files that need to be exported + in the document portal for the receiving app. But it does require + the passed-in file descriptors to be writable. + + Default: False + + * ``autostop`` (``b``) + + Whether to stop the transfer automatically after the first + org.freedesktop.portal.FileTransfer.RetrieveFiles() call. + + Default: True --> + @@ -105,7 +106,9 @@ + + @@ -127,6 +130,7 @@ --> + diff --git a/data/org.freedesktop.portal.GameMode.xml b/data/org.freedesktop.portal.GameMode.xml index c4c8878..e180d47 100644 --- a/data/org.freedesktop.portal.GameMode.xml +++ b/data/org.freedesktop.portal.GameMode.xml @@ -1,10 +1,13 @@ @@ -49,16 +53,21 @@ @@ -70,14 +79,20 @@ @result: Status of the request: 0 for success, -1 indicates an error Register a game with GameMode and thus request GameMode to - be activated. If the caller is running inside a sandbox with - pid namespace isolation, the pid will be translated to the - respective host pid. See the general introduction for details. + be activated. + + If the caller is running inside a sandbox with PID namespace + isolation, the @pid will be translated to the respective host PID. + + See the general introduction for details. + If the GameMode has already been requested for @pid before, this call will fail, i.e. result will be -1. - Will return: - 0 if the game with @pid was successfully registered, - -1 if the request was rejected by GameMode + + The @result will be one of: + + - `0` if the game with @pid was successfully registered + - `-1` if the request was rejected by GameMode --> @@ -88,14 +103,18 @@ @pid: Process id of the game to un-register @result: Status of the request: 0 for success, -1 indicates an error - Un-register a game from GameMode; if the call is successful - and there are no other games or clients registered, GameMode - will be deactivated. If the caller is running inside a sandbox - with pid namespace isolation, the pid will be translated to - the respective host pid. - Will return: - 0 if the game with @pid was successfully un-registered, - -1 if the request was rejected by GameMode + Un-register a game from GameMode. + + If the call is successful and there are no other games or clients + registered, GameMode will be deactivated. + + If the caller is running inside a sandbox with PID namespace + isolation, the @pid will be translated to the respective host PID. + + The @result will be one of: + + - `0` if the game with @pid was successfully un-registered + - `-1` if the request was rejected by GameMode --> diff --git a/data/org.freedesktop.portal.GlobalShortcuts.xml b/data/org.freedesktop.portal.GlobalShortcuts.xml index 2d77e36..3d66c88 100644 --- a/data/org.freedesktop.portal.GlobalShortcuts.xml +++ b/data/org.freedesktop.portal.GlobalShortcuts.xml @@ -2,10 +2,12 @@ + - + + - - - + - - + diff --git a/data/org.freedesktop.portal.Inhibit.xml b/data/org.freedesktop.portal.Inhibit.xml index e91bd22..1ae413e 100644 --- a/data/org.freedesktop.portal.Inhibit.xml +++ b/data/org.freedesktop.portal.Inhibit.xml @@ -2,10 +2,12 @@ + @@ -75,7 +73,7 @@ CreateMonitor: @window: the parent window @options: Vardict with optional further information - @handle: Object path for the #org.freedesktop.portal.Request object representing this call + @handle: Object path for the :ref:`org.freedesktop.portal.Request` object representing this call Creates a monitoring session. While this session is active, the caller will receive StateChanged signals @@ -84,51 +82,46 @@ A successfully created session can at any time be closed using org.freedesktop.portal.Session::Close, or may at any time be closed by the portal implementation, which will be signalled via - #org.freedesktop.portal.Session::Closed. + :ref:`org.freedesktop.portal.Session::Closed`. Supported keys in the @options vardict include: - - - handle_token s - - A string that will be used as the last element of the @handle. Must be a valid - object path element. See the #org.freedesktop.portal.Request documentation for - more information about the @handle. - - - - session_handle_token s - - A string that will be used as the last element of the session handle. Must be a valid - object path element. See the #org.freedesktop.portal.Session documentation for - more information about the session handle. - - - - - The following results get returned via the #org.freedesktop.portal.Request::Response signal: - - - session_handle o - - The session handle. An object path for the - #org.freedesktop.portal.Session object representing the created - session. - - - + + * ``handle_token`` (``s``) + + A string that will be used as the last element of the @handle. Must be a valid + object path element. See the :ref:`org.freedesktop.portal.Request` documentation for + more information about the @handle. + + * ``session_handle_token`` (``s``) + + A string that will be used as the last element of the session handle. Must be a valid + object path element. See the :ref:`org.freedesktop.portal.Session` documentation for + more information about the session handle. + + The following results get returned via the :ref:`org.freedesktop.portal.Request::Response` signal: + + * ``session_handle`` (``s``) + + The session handle. An object path for the + :ref:`org.freedesktop.portal.Session` object representing the created + session. + + .. note:: + The ``session_handle`` is an object path that was erroneously implemented + as ``s``. For backwards compatibility it will remain this type. This method was added in version 2 of this interface. --> + + + + + + @@ -292,7 +262,7 @@ + + + + + + + @@ -566,11 +522,9 @@ Currently defined types are: - - 1: KEYBOARD - 2: POINTER - 4: TOUCHSCREEN - + - ``1``: KEYBOARD + - ``2``: POINTER + - ``4``: TOUCHSCREEN --> diff --git a/data/org.freedesktop.portal.Location.xml b/data/org.freedesktop.portal.Location.xml index 35b40f3..b0be22a 100644 --- a/data/org.freedesktop.portal.Location.xml +++ b/data/org.freedesktop.portal.Location.xml @@ -2,10 +2,12 @@ + + + diff --git a/data/org.freedesktop.portal.MemoryMonitor.xml b/data/org.freedesktop.portal.MemoryMonitor.xml index c347581..fb53ea8 100644 --- a/data/org.freedesktop.portal.MemoryMonitor.xml +++ b/data/org.freedesktop.portal.MemoryMonitor.xml @@ -2,10 +2,12 @@ diff --git a/data/org.freedesktop.portal.NetworkMonitor.xml b/data/org.freedesktop.portal.NetworkMonitor.xml index 5359b0c..8ab9052 100644 --- a/data/org.freedesktop.portal.NetworkMonitor.xml +++ b/data/org.freedesktop.portal.NetworkMonitor.xml @@ -2,10 +2,12 @@ - + - + - + - + + - - - + + + diff --git a/data/org.freedesktop.portal.Notification.xml b/data/org.freedesktop.portal.Notification.xml index 5d91fb9..9ee91a5 100644 --- a/data/org.freedesktop.portal.Notification.xml +++ b/data/org.freedesktop.portal.Notification.xml @@ -2,10 +2,12 @@ + + * ``title`` (``s``) + + User-visible string to display as the title. + + This should be a short string, if it doesn't fit the UI it may + be truncated to fit on a single line. + + * ``body`` (``s``) + + User-visible string to display as the body. + + This can be a long string but if it doesn't fit the UI it may + be wrapped or/and truncated. + + * ``markup-body`` (``s``) + + The same as ``body`` but with support for markup formatting. + The markup is XML-based and supports a small subset of HTML + ``...``, ``...`` and ``...``. + + Any markup not supported, e.g. new lines, will be removed from + the string. In the future, the set of supported markup may be extended. + + This can be a long string but if it doesn't fit the UI it may + be wrapped or/and truncated. + + * ``icon`` (``v``) + + A serialized icon to add to the notification. The icon must pass + `icon validation `_ in order to be used. The format for + serialized icon is a tuple (sv) with the following supported keys: + + * ``themed`` (``as``) + + A themed icon containing an array of strings with the icon names. + + This is the same format as a serialized `GThemedIcon + `_ at the moment, + but this interoperability may change in the future. + + * ``bytes`` (``ay``) + + Since version 2, this is deprecated and should not be used. + Please use the `themed` or `file-descriptor` option to set an icon. + + This is the same format as a serialized `GBytesIcon + `_ at the moment, + but this interoperability may change in the future. + + * ``file-descriptor`` (``h``) + + A file descriptor to an image file in png, jpeg or svg form. + The file-descriptor used needs to be sealable, currently this is only + possible for file descriptors created with ``memfd_create()`` with + the ``MFD_ALLOW_SEALING`` flag set. + + For historical reasons, it is also possible to send a simple string + for themed icons with a single icon name. + + There may be further restrictions on the supported kinds of icons. + + * ``sound`` (``v``) + + A serialized sound to add to the notification. + Supported sound formats are ogg/opus, ogg/vorbis and wav/pcm. + + The format for + serialized sound is a tuple (sv) with the following supported keys: + + * ``file-descriptor`` (``h``) + + A file descriptor to a sound file. + The file-descriptor used needs to be sealable, currently this is only + possible for file descriptors created with ``memfd_create()`` with + the ``MFD_ALLOW_SEALING`` flag set. + + To play the default sound the string ``default`` can be passed. + To play no sound at all the string ``silent`` can be passed. + If this property isn't specified the notification server can decide + whether to play a sound. + + There may be further restrictions on the supported kinds of sounds. + + * ``priority`` (``s``) + + The priority for the notification. Supported values: + + - ``low`` + - ``normal`` + - ``high`` + - ``urgent`` + + * ``default-action`` (``s``) + + Name of an action that is exported by the application. This + action will be activated when the user clicks on the notification. + + * ``default-action-target`` (``v``) + + Target parameter to send along when activating the default action. + + * ``buttons`` (``aa{sv}``) + + Array of serialized buttons to add to the notification. The format for + serialized buttons is a vardict with the following supported keys: + + * ``label`` (``s``) + + User-visible label for the button. Mandatory, if no purpose + is specified. It is strongly recommended to always provide + sensible label. Buttons without a ``label`` are ignored + by the server when it doesn't understand the ``purpose`` or + is needed to display the button. + + * ``action`` (``s``) + + Name of an action that is exported by the application. The action + will be activated when the user clicks on the button. Mandatory. + + * ``target`` (``v``) + + Target parameter to send along when activating the action. + + * ``purpose`` (``s``) + + The ``purpose`` of the button. This information may be used by the + notification server to treat the button specially. + + Depending on the ``purpose`` other fields of the button may be ignored. + If the server doesn't understand the ``purpose`` it will be + ignored and the button will be shown as a normal button. + + Most standardized hints are defined as part of a ``category``. + Additional purposes may be defined by notification servers using + ``x-vendor`` prefix e.g. ``x-gnome.class.specific`` + + The following purposes are defined outside of a ``category``: + + * ``system.custom-alert``: + + Not a button in a strict sense. This action may be called, + depending on system policies, automatically by the notification + server whenever the notification is shown. + + This allows apps to use custom methods for notifying the user, + for example, to play audio from a special + source like a streaming service or a radio station. + + No ``label`` should be given when this purpose is used, so that + the server can ignore the button if it doesn't understand the purpose. + + * ``display-hint`` (``as``) + + An array of ways to display the notification. If none are set, or the notification server has its own policy, it is + free to decide how and where to display the notification. + + * ``transient`` + + The notification is displayed only as a banner and won't be kept + by the server in a tray. + + It's a programmer error to specify ``tray`` at the same time. + + * ``tray`` + + No banner for the notification will be displayed and + the notification is placed in the tray. + + It's a programmer error to specify ``transient`` at the same time. + + * ``persistent`` + + Make the notification persistent in the notification tray. + The user can’t dismiss it using the usual close button or gesture. + + Apps are only allowed to display persistent notifications + as long as they have a window. Once the last window of an app + is closed the persistent notification will be removed. + + * ``hide-on-lockscreen`` + + Don't show the notification on the lockscreen. + + * ``hide-content-on-lockscreen`` + + All content of the notification will be hidden on the lockscreen. + + * ``show-as-new`` + + If a notification with the same ``id`` of the app exists already + replace the previous notification, by removing the old notification + (including animation, etc) and adding a new notification. + + If this hint isn't specified the notification's content is updated + without any flickering. + + * ``category`` (``s``) + + The ``category`` describes the content of a notification. + A notification server may use this information to display the + notification specially. Some categories also include + button purposes that can be set for a button so that the notification + can know the purpose of the button. + + Additional categories and button purposes may be defined by notification servers + using ``x-vendor`` prefix e.g. ``x-gnome.class.specific`` + + The following categories are standarized so far: + + * ``im.received`` + + Intended for instant messaging apps displaying notifications for new messages. + + This category has the following button purposes: + + * ``im.reply-with-text``: + + Inline replies for instant messaging. + The user-provided text will be added to the response. + + The user response (``s``) will be placed as the second value + in the parameter array of exported actions. + For non-exported actions it will be placed as the third value + in the parameter array of #org.freedesktop.portal.Notification::ActionInvoked. + + * ``alarm.ringing`` + + Intended for alarm clock apps + + * ``call.incoming`` + + Intended for call apps to notify the user about an incoming call. + + This category has the following button purposes: + + * ``call.accept``: + + Accept the incoming call. + + * ``call.decline``: + + Decline the incoming call. + + * ``call.ongoing`` + + Intended for call apps while a call is ongoing. + + This type has the following button purposes: + + * ``call.hang-up``: + + Hang up the ongoing call. + + * ``call.enable-speakerphone``: + + Enable the speakerphone for the ongoing call. + + * ``call.disable-speakerphone``: + + Disable the speakerphone for the ongoing call. + + * ``call.unanswered`` + + Intended to be used by call apps when a call was missed. + + * ``weather.warning.extreme`` + + Intended to be used to display an extreme weather warning. + + * ``cellbroadcast.danger.extreme`` + + Intended to be used to display extreme danger warnings broadcasted by the cell network. + + * ``cellbroadcast.danger.severe`` + + Intended to be used to display severe danger warnings broadcasted by the cell network. + + * ``cellbroadcast.amber-alert`` + + Intended to be used to display amber alerts broadcasted by the cell network. + + * ``cellbroadcast.test`` + + Intended to be used to display tests broadcasted by the cell network. + + * ``os.battery.low`` + + Intended to be used to indicate that the system is low on battery. + + * ``browser.web-notification`` + + Intended to be used by browsers to mark notifications send by websites via + the `Notifications API `_. + --> + + + + + + diff --git a/data/org.freedesktop.portal.OpenURI.xml b/data/org.freedesktop.portal.OpenURI.xml index 0c42029..49958cb 100644 --- a/data/org.freedesktop.portal.OpenURI.xml +++ b/data/org.freedesktop.portal.OpenURI.xml @@ -2,10 +2,12 @@ + @@ -143,38 +125,35 @@ + @@ -182,10 +161,30 @@ + + + + + + + + + diff --git a/data/org.freedesktop.portal.PowerProfileMonitor.xml b/data/org.freedesktop.portal.PowerProfileMonitor.xml index daf487a..ecd748e 100644 --- a/data/org.freedesktop.portal.PowerProfileMonitor.xml +++ b/data/org.freedesktop.portal.PowerProfileMonitor.xml @@ -2,10 +2,12 @@ + + + + + + + + + + + + + + - - - - - - - - - diff --git a/data/org.freedesktop.portal.ProxyResolver.xml b/data/org.freedesktop.portal.ProxyResolver.xml index 4b39fc0..070eb98 100644 --- a/data/org.freedesktop.portal.ProxyResolver.xml +++ b/data/org.freedesktop.portal.ProxyResolver.xml @@ -2,10 +2,12 @@ diff --git a/data/org.freedesktop.portal.Realtime.xml b/data/org.freedesktop.portal.Realtime.xml index 1408291..2b53c89 100644 --- a/data/org.freedesktop.portal.Realtime.xml +++ b/data/org.freedesktop.portal.Realtime.xml @@ -2,10 +2,12 @@ + + + + + @@ -255,7 +233,7 @@ + + + + + + @@ -402,7 +379,7 @@ + @@ -427,7 +405,7 @@ + @@ -472,6 +451,7 @@ + @@ -481,11 +461,9 @@ A bitmask of available source types. Currently defined types are: - - 1: KEYBOARD - 2: POINTER - 4: TOUCHSCREEN - + - ``1``: KEYBOARD + - ``2``: POINTER + - ``4``: TOUCHSCREEN --> diff --git a/data/org.freedesktop.portal.Request.xml b/data/org.freedesktop.portal.Request.xml index c1abb4e..e8a2648 100644 --- a/data/org.freedesktop.portal.Request.xml +++ b/data/org.freedesktop.portal.Request.xml @@ -2,10 +2,12 @@ @@ -71,16 +79,15 @@ Emitted when the user interaction for a portal request is over. The @response indicates how the user interaction ended: - - 0: Success, the request is carried out - 1: The user cancelled the interaction - 2: The user interaction was ended in some other way - + + - 0: Success, the request is carried out + - 1: The user cancelled the interaction + - 2: The user interaction was ended in some other way --> - + diff --git a/data/org.freedesktop.portal.ScreenCast.xml b/data/org.freedesktop.portal.ScreenCast.xml index 7be37d6..8233242 100644 --- a/data/org.freedesktop.portal.ScreenCast.xml +++ b/data/org.freedesktop.portal.ScreenCast.xml @@ -2,10 +2,12 @@ + + + diff --git a/data/org.freedesktop.portal.Screenshot.xml b/data/org.freedesktop.portal.Screenshot.xml index f8927de..bb62218 100644 --- a/data/org.freedesktop.portal.Screenshot.xml +++ b/data/org.freedesktop.portal.Screenshot.xml @@ -2,10 +2,12 @@ + + diff --git a/data/org.freedesktop.portal.Secret.xml b/data/org.freedesktop.portal.Secret.xml index a647baf..8d8d9da 100644 --- a/data/org.freedesktop.portal.Secret.xml +++ b/data/org.freedesktop.portal.Secret.xml @@ -2,10 +2,12 @@ + diff --git a/data/org.freedesktop.portal.Session.xml b/data/org.freedesktop.portal.Session.xml index 8a902b2..648cbc0 100644 --- a/data/org.freedesktop.portal.Session.xml +++ b/data/org.freedesktop.portal.Session.xml @@ -2,10 +2,12 @@ + diff --git a/data/org.freedesktop.portal.Settings.xml b/data/org.freedesktop.portal.Settings.xml index 1ae6752..1e3e32e 100644 --- a/data/org.freedesktop.portal.Settings.xml +++ b/data/org.freedesktop.portal.Settings.xml @@ -2,10 +2,12 @@ @@ -69,9 +74,10 @@ If @namespaces is an empty array or contains an empty string it matches all. Globbing is supported but only for trailing sections, e.g. "org.example.*". --> - - - + + + + - + - - - + + + - - - - + + + + - - - - + + + + diff --git a/data/org.freedesktop.portal.Trash.xml b/data/org.freedesktop.portal.Trash.xml index cacbfd4..ba348b9 100644 --- a/data/org.freedesktop.portal.Trash.xml +++ b/data/org.freedesktop.portal.Trash.xml @@ -2,10 +2,12 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/org.freedesktop.portal.Wallpaper.xml b/data/org.freedesktop.portal.Wallpaper.xml index a214690..0ac6a91 100644 --- a/data/org.freedesktop.portal.Wallpaper.xml +++ b/data/org.freedesktop.portal.Wallpaper.xml @@ -2,10 +2,12 @@ + + diff --git a/debian/changelog b/debian/changelog index 9d96439..f033efa 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,192 @@ +xdg-desktop-portal (1.20.3+ds-1) unstable; urgency=medium + + * New upstream stable release + - Fix a crash when a Flatpak app does not export a .desktop file + with the same name as its app-ID (Closes: #1106091) + - Allow Flatpak and Snap apps to use portal APIs even if they do not + have a .desktop file matching the app-ID + - Fix file descriptors being closed too soon, resulting in log noise + + -- Simon McVittie Wed, 21 May 2025 11:05:04 +0100 + +xdg-desktop-portal (1.20.1+ds-2) unstable; urgency=medium + + * Release to unstable (Closes: #1105889) + + -- Simon McVittie Fri, 16 May 2025 17:21:45 +0100 + +xdg-desktop-portal (1.20.1+ds-1) experimental; urgency=medium + + * New upstream stable release + - Fix installation of dynamic launcher .desktop files, for example + for browsers in webapp mode (Closes: #1105889) + - Load portal backends from XDG_DATA_HOME and all XDG_DATA_DIRS, + allowing use of locally-installed (unpackaged) backends + - Re-order bubblewrap arguments for icon/sound validators to avoid + mounting a tmpfs that hides the working directory if run from /tmp, + for example during testing + - Fix thread-safety in the host app registration portal by doing + everything from the main thread + - Input capture moves from ACTIVE to ENABLED state when deactivated, + instead of staying ACTIVE + - Improve log messages for the realtime interface + - Stop the D-Bus services before the D-Bus session bus stops, + avoiding spurious warnings in the system log + - Refactor initialization of app info to reduce duplication and + ensure that file descriptors are marked close-on-exec + - Log a warning if the documents portal mount point cannot be found + - Highlight xdg-desktop-portal errors if outputting to a terminal + - Send xdg-desktop-portal log messages to stderr, not stdout + - Translation updates: bg, ca + - Documentation updates + * d/copyright: Use GNU web address instead of FSF's former postal address + * d/copyright: Be clearer about the Library vs. Lesser General Public + License + * d/copyright: Record that some files are GPL-2+ + * d/xdg-desktop-portal-tests.lintian-overrides: + Add an override for a false positive in the tests + * Release to experimental for wider testing during the Debian 13 freeze + + -- Simon McVittie Fri, 16 May 2025 13:47:10 +0100 + +xdg-desktop-portal (1.20.0+ds-2) unstable; urgency=medium + + * d/rules: Only enable as-installed tests if we're building that package + * d/rules: Enable tests whenever we are building as-installed tests. + Even if we will not actually run the tests at build-time, we still + need to enable the option if we want to install the as-installed + tests, and for that we need a subset of the tests' dependencies. + Thanks to Jeremy Bícha (Closes: #1101046) + * Standards-Version: 4.7.2 (no changes required) + + -- Simon McVittie Sat, 22 Mar 2025 20:47:16 +0000 + +xdg-desktop-portal (1.20.0+ds-1) unstable; urgency=medium + + * New upstream stable release + + -- Simon McVittie Thu, 20 Feb 2025 11:54:43 +0000 + +xdg-desktop-portal (1.19.4+ds-1) unstable; urgency=medium + + * New upstream development release + * d/patches: Drop patches that were applied upstream + + -- Simon McVittie Mon, 17 Feb 2025 13:31:49 +0000 + +xdg-desktop-portal (1.19.3+ds-1) unstable; urgency=medium + + [ Simon McVittie ] + * New upstream development release + * d/control: libportal-dev is no longer used for unit tests + * d/x-d-p.docs: NEWS is now named NEWS.md + * d/rules: Rename the pytest option to tests, following upstream + * d/control: Explicitly build-depend on python3-dbus. + It's used by dbusmock, and the upstream build system checks for it. + * d/p/notification-Escape-the-href-attribute-of-links-when-re-s.patch, + d/p/tests-Assert-that-href-attributes-are-escaped-correctly.patch, + d/p/notification-Escape-the-body-text-of-the-markup-body-when.patch, + d/p/tests-Assert-that-markup-body-text-is-escaped-correctly.patch: + Add patches from upstream to fix handling of XML in notifications + * Release to unstable (Closes: #1095098). + While this is still a development release, upstream intends to + release 1.20.x in time for the Debian 13 freeze, so it's better to + have wider testing at this stage. + + [ Jeremy Bícha ] + * Update Homepage + + -- Simon McVittie Fri, 14 Feb 2025 18:08:24 +0000 + +xdg-desktop-portal (1.19.2+ds-1) experimental; urgency=medium + + * New upstream development release + * d/rules, d/tests: Set XDP_TEST_IN_CI instead of TEST_IN_CI. + The environment variable used to skip flaky tests and extend some + other tests' timeouts was renamed in this upstream development branch. + * d/control: Add dependencies to run pytest-based tests from autopkgtest + * d/copyright: Update + * d/p/debian/doc-Use-system-copy-of-Inter-Variable-font.patch: + Refresh patch + + -- Simon McVittie Tue, 21 Jan 2025 14:45:35 +0000 + +xdg-desktop-portal (1.19.1+ds-1) experimental; urgency=medium + + * New upstream development release + * Drop patches that were included in 1.19.1 + * d/p/tests-Set-XDG_DATA_DIRS-for-as-installed-testing.patch: + Add patch to fix test-portals-openuri.test during autopkgtest + * d/copyright: Update + * d/watch: Fix dversionmangle processing for +ds versions + * d/control: Update build-dependencies + * d/control: Depend on GStreamer plugins for WAV, Ogg Vorbis, Ogg Opus + * d/rules: Disable pytest-based tests if built with nocheck + * d/rules: Disable sandboxing of sound validation during build-time + testing, because bubblewrap can't work in a buildd chroot + * d/x-d-p.install: Install new x-d-p-validate-sound helper + * d/salsa-ci.yml: Use recommended recipe + * d/tests/control: Try treating installed-tests as stable. + We can mark them as flaky before re-uploading to unstable if this + turns out not to be the case. + * d/rules: Extend test timeouts on non-x86. + Some -ports architectures use qemu for buildds, which can be very + slow, and there is no official way to tell which architectures + do this. + + -- Simon McVittie Thu, 02 Jan 2025 21:33:06 +0000 + +xdg-desktop-portal (1.19.0+ds-1) experimental; urgency=medium + + * New upstream development release + * d/copyright: Update + * Repack tarball to exclude vendored Inter Variable font from + documentation + * d/p/debian/doc-Use-system-copy-of-Inter-Variable-font.patch: + Add a patch to avoid using the vendored Inter Variable font, which + we remove + * d/copyright, d/watch: Update for exclusion of vendored web font + * Remove obsolete Lintian override for #1031037, fixed in + Lintian 2.117.0 + * d/control, d/x-d-p-dev.docs, d/x-d-p-dev.doc-base: + Build HTML documentation using Sphinx; enable dh_sphinxdoc + * d/rules: Update Meson options. + sandboxed-image-validation is now a feature rather than a boolean. + * d/p/xdp_validate_icon-Assign-argument-indices-automatically.patch, + d/p/xdp_validate_icon-Allow-sandboxing-of-the-validator-to-be.patch, + d/rules: + Disable sandboxing of icon validation during build-time testing. + Some buildds build in a chroot, and bubblewrap can't work in that + environment. + * d/control: Add librsvg2-common to Recommends, Build-Depends, + test Depends. This is needed for SVG icon validation. + * d/control: Explicitly build-depend on bubblewrap + * d/control, d/rules: Only build documentation in arch-indep builds + * d/rules: Disable man pages under nodoc build profile + * Standards-Version: 4.7.0 (no changes required) + + -- Simon McVittie Wed, 30 Oct 2024 19:46:16 +0000 + +xdg-desktop-portal (1.18.4-2) unstable; urgency=medium + + * d/control, d/gbp.conf: Branch for changes targeting trixie. + The debian/latest branch has 1.19.x development releases, which are + packaged in experimental but haven't reached a sufficiently frozen + status to be considered for inclusion in Debian 13 yet. + * d/patches: Backport patches from 1.19.0 to fix test failure with + libportal 0.9.0 (Closes: #1091290) + * d/patches: Update to upstream 1.18.x branch commit 1.18.4-3-g45b5f7a4 + - Improve ability to use camera portal from non-sandboxed host apps + - Add a missing NEWS entry + * d/control: Explicitly build-depend on bubblewrap + * d/control, d/rules: Only build documentation in arch-indep builds + * d/rules: Disable man pages under nodoc build profile + * d/x-d-p.lintian-overrides: Remove obsolete Lintian override for #1031037 + * d/control: Standards-Version: 4.7.0 (no changes required) + * d/salsa-ci.yml: Use recommended recipe + + -- Simon McVittie Wed, 01 Jan 2025 18:51:00 +0000 + xdg-desktop-portal (1.18.4-1) unstable; urgency=medium * New upstream stable release diff --git a/debian/control b/debian/control index a01b1ae..24fc706 100644 --- a/debian/control +++ b/debian/control @@ -5,9 +5,12 @@ Maintainer: Utopia Maintenance Team , Build-Depends: + bubblewrap, dbus-daemon, debhelper-compat (= 13), geoclue-2.0 , + gstreamer1.0-plugins-good, + gstreamer1.0-tools, fuse3 , libcap2-bin , libgdk-pixbuf-2.0-dev, @@ -15,22 +18,32 @@ Build-Depends: libflatpak-dev, libfuse3-dev, libglib2.0-dev, + libgstreamer-plugins-base1.0-dev, + libgudev-1.0-dev, libjson-glib-dev, libpipewire-0.3-dev, - libportal-dev (>= 0.3), + librsvg2-common , libsystemd-dev, + libumockdev-dev , meson, pipewire , procps , - python3 , - python3-dbusmock , + python3 , + python3-dbus , + python3-dbusmock , python3-docutils , - python3-gi , - python3-pytest , - xmlto, + python3-gi , + python3-pytest , + umockdev , +Build-Depends-Indep: + dh-sequence-sphinxdoc , + furo , + python3-sphinx , + python3-sphinx-copybutton , + python3-sphinxext-opengraph , Rules-Requires-Root: no -Standards-Version: 4.6.2 -Homepage: https://github.com/flatpak/xdg-desktop-portal +Standards-Version: 4.7.2 +Homepage: https://flatpak.github.io/xdg-desktop-portal/ Vcs-Git: https://salsa.debian.org/debian/xdg-desktop-portal.git Vcs-Browser: https://salsa.debian.org/debian/xdg-desktop-portal @@ -41,8 +54,12 @@ Depends: bubblewrap, default-dbus-session-bus | dbus-session-bus, fuse3, + gstreamer1.0-plugins-base, + gstreamer1.0-plugins-good, ${misc:Depends}, ${shlibs:Depends}, +Recommends: + librsvg2-common, Description: desktop integration portal for Flatpak and Snap xdg-desktop-portal provides a portal frontend service for Flatpak, Snap, and possibly other desktop containment/sandboxing frameworks. This service @@ -64,9 +81,14 @@ Description: desktop integration portal for Flatpak and Snap Package: xdg-desktop-portal-dev Architecture: all Multi-Arch: foreign +Built-Using: + ${sphinxdoc:Built-Using}, Depends: ${misc:Depends}, ${shlibs:Depends}, + ${sphinxdoc:Depends}, +Recommends: + fonts-inter-variable, Description: desktop integration portal - development files xdg-desktop-portal provides a portal frontend service for Flatpak, Snap, and possibly other desktop containment/sandboxing frameworks. This service @@ -79,15 +101,21 @@ Description: desktop integration portal - development files xdg-desktop-portal-gtk. Package: xdg-desktop-portal-tests +Build-Profiles: Architecture: any Depends: dbus-daemon, geoclue-2.0, + gir1.2-umockdev-1.0, libcap2-bin, + librsvg2-common, pipewire, procps, python3, + python3-dbusmock, python3-gi, + python3-pytest, + umockdev, xdg-desktop-portal, ${misc:Depends}, ${shlibs:Depends}, diff --git a/debian/copyright b/debian/copyright index 9ec0804..c5db116 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,21 +1,37 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: xdg-desktop-portal Source: https://github.com/flatpak/xdg-desktop-portal/releases +Files-Excluded: doc/_static/inter.woff2 Files: * Copyright: - © 2010 Codethink Limited - © 2013-2019 Red Hat, Inc - © 2016 Free Software Foundation, Inc. - © 2016 Piotr Drag - © 2016 Aviary.pl - © 2017 Jan Alexander Steffens - © 2018-2021 Igalia S.L. - © 2022 Aleix Pol Gonzalez - © 2022 Endless OS Foundation, LLC - © 2022 Georges Basile Stavracas Neto -License: LGPL-2+ + 2022 Aleix Pol Gonzalez + 2016 Aviary.pl + 2010 Codethink Limited + 2021 Collabora Ltd. + 2020-2022 Endless OS Foundation, LLC + 2016 Free Software Foundation, Inc. + 2022 Georges Basile Stavracas Neto + 2023-2024 GNOME Foundation Inc. + 2022 Google LLC + 2018-2023 Igalia S.L. + 2017 Jan Alexander Steffens + 2021-2022 Matthew Leeds + 2016 Piotr Drag + 2013-2024 Red Hat, Inc + 2025 XDG Desktop Portal authors +License: LGPL-2.1+ + +Files: + src/xdp-app-launch-context.c + src/xdp-app-launch-context.h + src/xdp-background-monitor.c + src/xdp-background-monitor.h +Copyright: + 2022 Georges Basile Stavracas Neto + 2024 Red Hat, Inc +License: GPL-2+ Files: doc/website/* @@ -23,10 +39,19 @@ Copyright: none claimed License: CC0-1.0 Files: - src/flatpak-instance.* + .github/workflows/check-potfiles.sh Copyright: - © 2018 Red Hat, Inc -License: LGPL-2.1+ + 2020-2023 Florian Müllner + 2024 Corey Berla + 2024 Matthijs Velsink +License: probably-GPL-2 + This file originated in gnome-shell, from which it was copied via + nautilus and gnome-control-center into xdg-desktop-portal, with no + explicit license specified. The actual license is unclear, but it's + either GPL-2 or LGPL-2.1, with or without the "or any later version" + clause. On a Debian system, the relevant licenses can be found in + and + . Files: debian/* @@ -39,6 +64,23 @@ License: CC0-1.0 On Debian systems, the full text of the Creative Commons Zero 1.0 Universal public domain grant can be found in '/usr/share/common-licenses/CC0-1.0'. +License: GPL-2+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . +Comment: + On Debian systems, the full text of the GNU General Public License + version 2 can be found in the file '/usr/share/common-licenses/GPL-2'. + License: LGPL-2+ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -51,11 +93,15 @@ License: LGPL-2+ Lesser General Public License for more details. . You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + License along with this library. If not, see . Comment: + The oldest version of the Lesser General Public License under that name + was version 2.1. + . On Debian systems, the full text of the GNU Lesser General Public License - version 2.1 can be found in the file '/usr/share/common-licenses/LGPL-2.1'. + version 2.1 can be found in the file /usr/share/common-licenses/LGPL-2.1, + and its predecessor the GNU Library General Public License version 2 can + be found in /usr/share/common-licenses/LGPL-2. License: LGPL-2.1+ This library is free software; you can redistribute it and/or @@ -69,8 +115,7 @@ License: LGPL-2.1+ Lesser General Public License for more details. . You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + License along with this library. If not, see . Comment: On Debian systems, the full text of the GNU Lesser General Public License version 2.1 can be found in the file '/usr/share/common-licenses/LGPL-2.1'. diff --git a/debian/patches/debian/doc-Use-system-copy-of-Inter-Variable-font.patch b/debian/patches/debian/doc-Use-system-copy-of-Inter-Variable-font.patch new file mode 100644 index 0000000..8b1372e --- /dev/null +++ b/debian/patches/debian/doc-Use-system-copy-of-Inter-Variable-font.patch @@ -0,0 +1,79 @@ +From: Simon McVittie +Date: Tue, 15 Oct 2024 14:25:07 +0100 +Subject: doc: Use system copy of Inter Variable font + +Forwarded: not-needed, Debian-specific +--- + doc/_static/xdg.css | 21 ++++++--------------- + 1 file changed, 6 insertions(+), 15 deletions(-) + +diff --git a/doc/_static/xdg.css b/doc/_static/xdg.css +index f82f89c..5c6e773 100644 +--- a/doc/_static/xdg.css ++++ b/doc/_static/xdg.css +@@ -6,15 +6,6 @@ https://pypi.org/project/furo/ + + /* FONTS */ + +-@font-face { +- font-family: "Inter var"; +- font-weight: 100 900; +- font-display: swap; +- font-style: normal; +- font-named-instance: "Regular"; +- src: url("inter.woff2") format("woff2"); +-} +- + :root { + --rounded-corner: 12px; + --blue1: rgb(153,193,241); +@@ -97,7 +88,7 @@ html, body { + margin: 0; + padding: 0; + font-size: 16px; +- font-family: "Inter var", sans-serif; ++ font-family: "Inter Variable", "Inter var", sans-serif; + font-weight: 400; + line-height: 1.6; + } +@@ -122,35 +113,35 @@ h1 { + font-size: 2rem; + margin-top: '0'; + margin-bottom: 1.5rem; +- font-family: "Inter var", sans-serif; ++ font-family: "Inter Variable", "Inter var", sans-serif; + } + h2 { + font-weight: 800; + font-size: 1.7rem; + margin-top: 2.5rem; + margin-bottom: 1.2rem; +- font-family: "Inter var", sans-serif; ++ font-family: "Inter Variable", "Inter var", sans-serif; + } + h3 { + font-weight: 800; + font-size: 1.3rem; + margin-top: 2.2rem; + margin-bottom: 1rem; +- font-family: "Inter var", sans-serif; ++ font-family: "Inter Variable", "Inter var", sans-serif; + } + h4 { + font-weight: 700; + font-size: 1.1rem; + margin-top: 1rem; + margin-bottom: 1rem; +- font-family: "Inter var", sans-serif; ++ font-family: "Inter Variable", "Inter var", sans-serif; + } + h5 { + font-weight: 700; + font-size: 1rem; + margin-top: 1rem; + margin-bottom: 1rem; +- font-family: "Inter var", sans-serif; ++ font-family: "Inter Variable", "Inter var", sans-serif; + } + h2,h3,h4,h5 { opacity: 0.7; } + diff --git a/debian/patches/series b/debian/patches/series index e69de29..e0631e6 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -0,0 +1 @@ +debian/doc-Use-system-copy-of-Inter-Variable-font.patch diff --git a/debian/rules b/debian/rules index 32e383e..34250f3 100755 --- a/debian/rules +++ b/debian/rules @@ -9,22 +9,58 @@ built_binaries := $(shell dh_listpackages) CONFFLAGS := +ifneq ($(filter %-dev,$(built_binaries)),) +CONFFLAGS += -Ddocumentation=enabled +else +CONFFLAGS += -Ddocumentation=disabled +endif + +ifneq ($(filter %-tests,$(built_binaries)),) +CONFFLAGS += -Dinstalled-tests=true +else +CONFFLAGS += -Dinstalled-tests=false +endif + +# We must enable tests whenever we want either build-time or as-installed tests +ifneq ($(filter nocheck,$(DEB_BUILD_OPTIONS))$(filter %-tests,$(built_binaries)),nocheck) +CONFFLAGS += -Dtests=enabled +else +CONFFLAGS += -Dtests=disabled +endif + +ifeq ($(filter nodoc,$(DEB_BUILD_PROFILES)),) +CONFFLAGS += -Dman-pages=enabled +else +CONFFLAGS += -Dman-pages=disabled +endif + %: dh $@ --buildsystem=meson override_dh_auto_configure: dh_auto_configure -- \ -Dauto_features=enabled \ - -Dinstalled-tests=true \ - -Dsandboxed-image-validation=true \ + -Dsandboxed-image-validation=enabled \ $(CONFFLAGS) $(NULL) +ifneq ($(filter amd64 i386,$(DEB_HOST_ARCH_CPU)),) +test_timeout_multiplier = 1 +else +test_timeout_multiplier = 10 +endif + override_dh_auto_test: ifeq ($(filter nocheck,$(DEB_BUILD_OPTIONS)),) G_MESSAGES_DEBUG=all \ - TEST_IN_CI=1 \ - dh_auto_test --no-parallel + XDP_TEST_IN_CI=1 \ + XDP_VALIDATE_ICON_INSECURE=1 \ + XDP_VALIDATE_SOUND_INSECURE=1 \ + dh_auto_test \ + --no-parallel \ + -- \ + --timeout-multiplier $(test_timeout_multiplier) \ + $(NULL) endif # debhelper >= 13.4 makes all of /usr/libexec executable, which is not diff --git a/debian/salsa-ci.yml b/debian/salsa-ci.yml index 0c22dc4..8424db4 100644 --- a/debian/salsa-ci.yml +++ b/debian/salsa-ci.yml @@ -1,3 +1,3 @@ +--- include: - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml diff --git a/debian/tests/control b/debian/tests/control index 8094e12..7bc71d8 100644 --- a/debian/tests/control +++ b/debian/tests/control @@ -1,5 +1,5 @@ Tests: gnome-desktop-testing -Restrictions: flaky, isolation-machine +Restrictions: isolation-machine Depends: gnome-desktop-testing, xdg-desktop-portal-tests, diff --git a/debian/tests/gnome-desktop-testing b/debian/tests/gnome-desktop-testing index 27ca6f9..093e868 100755 --- a/debian/tests/gnome-desktop-testing +++ b/debian/tests/gnome-desktop-testing @@ -7,6 +7,6 @@ exec 2>&1 export PATH="$PATH:/sbin:/usr/sbin" # Some tests are skipped or have longer timeouts in CI -export TEST_IN_CI=1 +export XDP_TEST_IN_CI=1 exec gnome-desktop-testing-runner xdg-desktop-portal diff --git a/debian/watch b/debian/watch index 28c5196..b53ed96 100644 --- a/debian/watch +++ b/debian/watch @@ -1,9 +1,10 @@ version=4 -# Upstream releases official Autotools 'make dist' tarballs, so we use -# those in preference to git tags +# Upstream releases official tarballs, so we use those in preference to +# git tags opts="\ compression=xz, \ - dversionmangle=s/\+(?:git)?[0-9]*(?:\+g[0-9a-f]*)//, \ + dversionmangle=s/\+(?:git)?[0-9]+(?:\+g[0-9a-f]*)?//;s/[+~](?:dfsg|ds)[0-9]*//, \ downloadurlmangle=s#/tag/#/download/#;s#(v?@ANY_VERSION@)$#$1/@PACKAGE@-$2.tar.xz#, \ - filenamemangle=s#v?@ANY_VERSION@#@PACKAGE@-$1.tar.xz#" \ + filenamemangle=s#v?@ANY_VERSION@#@PACKAGE@-$1.tar.xz#, \ + repacksuffix=+ds" \ https://github.com/flatpak/@PACKAGE@/tags .*/releases/tag/v?@ANY_VERSION@ diff --git a/debian/xdg-desktop-portal-dev.doc-base b/debian/xdg-desktop-portal-dev.doc-base index bcbb95b..e3e7ad3 100644 --- a/debian/xdg-desktop-portal-dev.doc-base +++ b/debian/xdg-desktop-portal-dev.doc-base @@ -4,5 +4,5 @@ Author: Matthias Clasen Section: Programming Format: HTML -Index: /usr/share/doc/xdg-desktop-portal/portal-docs.html -Files: /usr/share/doc/xdg-desktop-portal/portal-docs.html /usr/share/doc/xdg-desktop-portal/docbook.css +Index: /usr/share/doc/xdg-desktop-portal-dev/html/index.html +Files: /usr/share/doc/xdg-desktop-portal-dev/html/*.html diff --git a/debian/xdg-desktop-portal-dev.docs b/debian/xdg-desktop-portal-dev.docs new file mode 100644 index 0000000..28fcd6c --- /dev/null +++ b/debian/xdg-desktop-portal-dev.docs @@ -0,0 +1 @@ +obj-*/doc/html diff --git a/debian/xdg-desktop-portal-dev.install b/debian/xdg-desktop-portal-dev.install index 62e5ec5..b56a12e 100644 --- a/debian/xdg-desktop-portal-dev.install +++ b/debian/xdg-desktop-portal-dev.install @@ -1,4 +1,2 @@ usr/share/dbus-1/interfaces -usr/share/doc/xdg-desktop-portal/docbook.css -usr/share/doc/xdg-desktop-portal/portal-docs.html usr/share/pkgconfig/xdg-desktop-portal.pc diff --git a/debian/xdg-desktop-portal-dev.links b/debian/xdg-desktop-portal-dev.links new file mode 100644 index 0000000..68ca494 --- /dev/null +++ b/debian/xdg-desktop-portal-dev.links @@ -0,0 +1 @@ +usr/share/doc/xdg-desktop-portal-dev/html usr/share/doc/xdg-desktop-portal/html diff --git a/debian/xdg-desktop-portal-tests.lintian-overrides b/debian/xdg-desktop-portal-tests.lintian-overrides new file mode 100644 index 0000000..c3793e8 --- /dev/null +++ b/debian/xdg-desktop-portal-tests.lintian-overrides @@ -0,0 +1,2 @@ +# library code, which is imported and not executed directly +xdg-desktop-portal-tests: script-not-executable [usr/libexec/installed-tests/xdg-desktop-portal/tests/__init__.py] diff --git a/debian/xdg-desktop-portal.docs b/debian/xdg-desktop-portal.docs index edc0071..212b8a7 100644 --- a/debian/xdg-desktop-portal.docs +++ b/debian/xdg-desktop-portal.docs @@ -1 +1 @@ -NEWS +NEWS.md diff --git a/debian/xdg-desktop-portal.install b/debian/xdg-desktop-portal.install index 8d8fa5d..5b94900 100644 --- a/debian/xdg-desktop-portal.install +++ b/debian/xdg-desktop-portal.install @@ -2,6 +2,7 @@ usr/lib/systemd/user usr/libexec/xdg-desktop-portal usr/libexec/xdg-desktop-portal-rewrite-launchers usr/libexec/xdg-desktop-portal-validate-icon +usr/libexec/xdg-desktop-portal-validate-sound usr/libexec/xdg-document-portal usr/libexec/xdg-permission-store usr/share/dbus-1/services diff --git a/debian/xdg-desktop-portal.lintian-overrides b/debian/xdg-desktop-portal.lintian-overrides deleted file mode 100644 index b6add39..0000000 --- a/debian/xdg-desktop-portal.lintian-overrides +++ /dev/null @@ -1,2 +0,0 @@ -# https://bugs.debian.org/1031037 -no-manual-page [usr/libexec/*] diff --git a/doc/_static/bullet.svg b/doc/_static/bullet.svg new file mode 100644 index 0000000..e753411 --- /dev/null +++ b/doc/_static/bullet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/_static/card.png b/doc/_static/card.png new file mode 100644 index 0000000..1374749 Binary files /dev/null and b/doc/_static/card.png differ diff --git a/doc/_static/toc_bullet.svg b/doc/_static/toc_bullet.svg new file mode 100644 index 0000000..d4a7733 --- /dev/null +++ b/doc/_static/toc_bullet.svg @@ -0,0 +1,39 @@ + + + + + + + + diff --git a/doc/_static/xdg-portal-dark.png b/doc/_static/xdg-portal-dark.png new file mode 100644 index 0000000..de29a12 Binary files /dev/null and b/doc/_static/xdg-portal-dark.png differ diff --git a/doc/_static/xdg-portal-light.png b/doc/_static/xdg-portal-light.png new file mode 100644 index 0000000..d97486d Binary files /dev/null and b/doc/_static/xdg-portal-light.png differ diff --git a/doc/_static/xdg.css b/doc/_static/xdg.css new file mode 100644 index 0000000..f82f89c --- /dev/null +++ b/doc/_static/xdg.css @@ -0,0 +1,452 @@ +/* XDG CSS theme for Sphinx + Furo + +https://www.sphinx-doc.org +https://pypi.org/project/furo/ +*/ + +/* FONTS */ + +@font-face { + font-family: "Inter var"; + font-weight: 100 900; + font-display: swap; + font-style: normal; + font-named-instance: "Regular"; + src: url("inter.woff2") format("woff2"); +} + +:root { + --rounded-corner: 12px; + --blue1: rgb(153,193,241); + --blue2: rgb(98,160,234); + --blue3: rgb(53,132,228); + --blue4: rgb(28,113,216); + --blue5: rgb(26,95,180); + --green1: rgb(143,240,164); + --green2: rgb(87,227,137); + --green3: rgb(51,209,122); + --green4: rgb(46,194,126); + --green5: rgb(38,162,105); + --yellow1: rgb(249,240,107); + --yellow2: rgb(248,228,92); + --yellow3: rgb(246,211,45); + --yellow4: rgb(245,194,17); + --yellow5: rgb(229,165,10); + --orange1: rgb(255,190,111); + --orange2: rgb(255,163,72); + --orange3: rgb(255,120,0); + --orange4: rgb(230,97,0); + --orange5: rgb(198,70,0); + --red1: rgb(246,97,81); + --red2: rgb(237,51,59); + --red3: rgb(224,27,36); + --red4: rgb(192,28,40); + --red5: rgb(165,29,45); + --purple1: rgb(220,138,221); + --purple2: rgb(192,97,203); + --purple3: rgb(145,65,172); + --purple4: rgb(129,61,156); + --purple5: rgb(97,53,131); + --brown1: rgb(205,171,143); + --brown2: rgb(181,131,90); + --brown3: rgb(152,106,68); + --brown4: rgb(134,94,60); + --brown5: rgb(99,69,44); + --light1: rgb(255,255,255); + --light2: rgb(246,245,244); + --light3: rgb(222,221,218); + --light4: rgb(192,191,188); + --light5: rgb(154,153,150); + --dark1: rgb(119,118,123); + --dark2: rgb(94,92,100); + --dark3: rgb(61,56,70); + --dark4: rgb(36,31,49); + --dark5: rgb(0,0,0); + --text: #f6f5f4; + --tocbg: var(--light2); + --tocfg: var(--dark3); + --borders: var(--light3); + --link: var(--blue5); + --link-underline: var(--blue1); +} + +::selection { + background-color: rgba(153,193,241,0.5); +} + +@media (prefers-color-scheme: dark) { + :root { + --tocbg: var(--dark5); + --tocfg: var(--light5); + --borders: var(--dark3); + --link: var(--blue1); + --link-underline: var(--blue5); + } + ::selection { + background-color: rgba(26,95,180,0.4); + } +} + +* { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +html, body { + margin: 0; + padding: 0; + font-size: 16px; + font-family: "Inter var", sans-serif; + font-weight: 400; + line-height: 1.6; +} + @media (min-width: 700px) { + html, body { + font-size: 18px; + } + } + +a { + color: var(--link); + text-decoration-color: var(--link-underline); +} + +p strong { + font-weight: 600; + opacity: 0.9; +} + +h1 { + font-weight: 500; + font-size: 2rem; + margin-top: '0'; + margin-bottom: 1.5rem; + font-family: "Inter var", sans-serif; +} +h2 { + font-weight: 800; + font-size: 1.7rem; + margin-top: 2.5rem; + margin-bottom: 1.2rem; + font-family: "Inter var", sans-serif; +} +h3 { + font-weight: 800; + font-size: 1.3rem; + margin-top: 2.2rem; + margin-bottom: 1rem; + font-family: "Inter var", sans-serif; +} +h4 { + font-weight: 700; + font-size: 1.1rem; + margin-top: 1rem; + margin-bottom: 1rem; + font-family: "Inter var", sans-serif; +} +h5 { + font-weight: 700; + font-size: 1rem; + margin-top: 1rem; + margin-bottom: 1rem; + font-family: "Inter var", sans-serif; +} + h2,h3,h4,h5 { opacity: 0.7; } + +ul.simple { + margin: 1rem 1rem 3rem; +} + + ul.simple li { + list-style-image: url(bullet.svg); + margin-inline-end: 1ch; + margin-bottom: 1rem; + } + ul.simple li:hover::marker { } + +.related-pages { + font-size: 10pt; + font-weight: 800; + display: flex; + flex-direction: row-reverse; + align-items: stretch; + justify-content: space-between; +} + + .related-pages a .title { + color: var(--dark1); + } + .related-pages a svg { + width: 32px; height: 32px; + } + + .prev-page { + border-radius: 8px; + padding: 8px 24px 8px 8px; + + } + .next-page { + border-radius: 8px; + padding: 8px 8px 8px 24px; + } + .related-pages a.prev-page, + .related-pages a.next-page { + float: none; + max-width: inherit; /* cancel furo's 50% */ + } + .related-pages a.prev-page:hover, .related-pages a.next-page:hover { + background-color: var(--tocbg); + } + + @media (min-width: 700px) { + .related-pages { + font-size: 14pt; + } + .prev-page { + padding: 16px 48px 16px 16px; + border-radius: 24px; + + } + .next-page { + padding: 16px 16px 16px 48px; + border-radius: 24px; + } + } + .related-pages a.prev-page:only-child, + .related-pages a.next-page:only-child, + .page-info { + /* align-self: flex-end; i wish */ + width: 100%; /* big buttons instead */ + } + +.toctree-wrapper ul { + margin: 0; padding: 0; +} + + .toctree-wrapper li { + list-style: none; + margin: 0 0 1rem 0; + } + .toctree-wrapper li.toctree-l1 { + font-size: 14pt; + font-weight: 600; + + } + .toctree-wrapper li.toctree-l1 > a { color: var(--tocfg); } + + .toctree-wrapper li.toctree-l2 { + list-style-image: url(toc_bullet.svg); + margin: 0; + font-size: 12pt; + font-weight: 400; + padding: 0.2rem; + } + .toctree-wrapper > ul ul { + margin-top: 1rem; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); + gap: .2rem; + } + .toctree-wrapper > ul ul a { + background-color: transparent; + } +/* tiled TOC with images */ + +ul.tiled-toc { + display: grid; + gap: 1rem; + grid-auto-flow: row; + margin: 1rem 0; padding: 0; + text-align: center; + margin-top: 2rem; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); +} + ul.tiled-toc li { + display: block; + list-style: none; + } + ul.tiled-toc li img { + display: block; + width: 100%; + image-rendering: crisp-edges; + image-rendering: pixelated; + } + ul.tiled-toc li img::after { + content: ""; + } + + ul.tiled-toc a { + color: black; + text-decoration: none; + font-weight: 600; + } + + @media (prefers-color-scheme: dark) { + ul.tiled-toc a { color: white; } + } + + +table.docutils { + font-size: 90%; + box-shadow: none; + border-collapse: collapse; + border-spacing: 0; + border-radius: 0; + text-align: left; + border-width: 0; + box-sizing: border-box; +} + /* reset crazy */ + table.docutils th, table.docutils tr, table.docutils td { + background-color: transparent; + text-align: left; + } + + table.docutils td, table.docutils th { + border-width: 0; + padding: .25rem; + } + table.docutils tr { + border-width: 0; + border-bottom: 1px solid var(--borders); + } + + table.docutils thead tr { + font-weight: 800; + border-width: 0; + box-sizing: inherit; + border-bottom: 2px solid var(--borders); + } + + .hig-palette-table.align-default { + display: table; + width: 100%; + } + + img.hig-palette-swatch { + display: block; + width: 42px; + aspect-ratio: 1 / 1; + border-radius: 50%; + } + + .hig-palette-table colgroup { display: none; } + .hig-palette-table .stub, + .hig-palette-table .head.stub { + width: 50px; + } + +video { + width: 100%; + height: auto; +} + +footer { + margin-bottom: 3rem; +} + +.sidebar-drawer { + +} + +.sidebar-brand-text { + font-family: Cantarell, -apple-system, BlinkMacSystemFont, "Helvetica", sans-serif; + font-size: 14pt; + font-weight: 800; +} + +.sidebar-search-container, +.sidebar-search { + border-radius: 99999px; + border: none; +} +.sidebar-search-container { + margin: 0 var(--sidebar-item-spacing-horizontal); + transition: background-color 0.2s linear; + background-color: rgba(0,0,0,0.06); +} +.sidebar-search-container:hover { + background-color: rgba(0,0,0,0.09); +} +.sidebar-search-container:focus-within { + background-color: rgba(0,0,0,0.12); +} + +@media (prefers-color-scheme: dark) { + .sidebar-search-container { + background-color: rgba(255,255,255,0.06); + } + .sidebar-search-container:hover { + background-color: rgba(255,255,255,0.09); + } + .sidebar-search-container:focus-within { + background-color: rgba(255,255,255,0.12); + } +} + +.sidebar-tree .toctree-l1, +.sidebar-tree .toctree-l2 { + margin: 1px 12px; + border-radius: 6px; + color: var(--link); +} + +.sidebar-tree .toctree-l1 > .reference { + color: var(--link); +} + +.sidebar-tree .toctree-l2 { + margin: 1px 0; + font-size: 90%; + text-wrap: balance; + overflow-wrap: break-word; + hyphens: manual; +} + +.sidebar-tree li > ul { + margin-left: 0; +} +.sidebar-tree li > ul .reference { + padding-left: calc(1.5*var(--sidebar-item-spacing-horizontal)); +} + +.sidebar-tree label[for^="toctree-checkbox"] { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.sidebar-tree li.toctree-l3 a { + margin: 1px 0; + padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) + var(--sidebar-item-spacing-vertical) calc(2*var(--sidebar-item-spacing-horizontal)) ; /* extra indentation */ +} + +.sidebar-tree .reference { + background: rgba(0,0,0,0); + transition: background 0.2s linear; + border-radius: 6px; + padding: var(--sidebar-item-spacing-vertical) calc(0.75*var(--sidebar-item-spacing-horizontal)); +} +.sidebar-tree .reference:hover, +.sidebar-tree .current > .reference:hover { + /* background: var(--color-background-hover); */ + background: rgba(0,0,0,0.03); +} + +.sidebar-tree .reference.current { + background: rgba(0,0,0,0.06); +} +.sidebar-tree .reference.current:hover { + background: rgba(0,0,0,0.09); +} + +.sidebar-tree .current-page > .reference { + font-weight: 600; +} + +.theme-toggle { + display: none; /* just go with auto */ +} diff --git a/doc/api-reference.rst b/doc/api-reference.rst new file mode 100644 index 0000000..fc64301 --- /dev/null +++ b/doc/api-reference.rst @@ -0,0 +1,69 @@ +API Reference +============= + +Portal interfaces are available to sandboxed applications with the default +filtered session bus access of Flatpak. + +Desktop portals appear under the bus name ``org.freedesktop.portal.Desktop`` +and the object path ``/org/freedesktop/portal/desktop`` on the session bus, +unless specified otherwise. + +Apps running on the host system have access to a special interface for +registering themselves with XDG Desktop Portal. Registering a host app with +XDG Desktop Portal overwrites the automatic detection based on the +`XDG cgroup pathname standardization for applications +`_. +This might improve the user experience when the host app was launched in a way +that doesn't follow the standard. See +:doc:`org.freedesktop.host.portal.Registry ` + +Disclaimer: The host app registry is expected to eventually be deprecated and +may be removed. Applications should gracefully handle interface or method no +longer being available to be forward compatible. App launchers, or apps +themselves, should place the app in a cgroup named according to specific naming +conventions. When the host app registry becomes deprecated, the details of the +replacement will be documented in :doc:`org.freedesktop.host.portal.Registry +`. + +.. toctree:: + :hidden: + + doc-org.freedesktop.host.portal.Registry.rst + +All apps have access to the portals below: + +.. toctree:: + :maxdepth: 1 + + doc-org.freedesktop.portal.Account.rst + doc-org.freedesktop.portal.Background.rst + doc-org.freedesktop.portal.Camera.rst + doc-org.freedesktop.portal.Clipboard.rst + doc-org.freedesktop.portal.Documents.rst + doc-org.freedesktop.portal.DynamicLauncher.rst + doc-org.freedesktop.portal.Email.rst + doc-org.freedesktop.portal.FileChooser.rst + doc-org.freedesktop.portal.FileTransfer.rst + doc-org.freedesktop.portal.GameMode.rst + doc-org.freedesktop.portal.GlobalShortcuts.rst + doc-org.freedesktop.portal.Inhibit.rst + doc-org.freedesktop.portal.InputCapture.rst + doc-org.freedesktop.portal.Location.rst + doc-org.freedesktop.portal.MemoryMonitor.rst + doc-org.freedesktop.portal.NetworkMonitor.rst + doc-org.freedesktop.portal.Notification.rst + doc-org.freedesktop.portal.OpenURI.rst + doc-org.freedesktop.portal.PowerProfileMonitor.rst + doc-org.freedesktop.portal.Print.rst + doc-org.freedesktop.portal.ProxyResolver.rst + doc-org.freedesktop.portal.Realtime.rst + doc-org.freedesktop.portal.RemoteDesktop.rst + doc-org.freedesktop.portal.Request.rst + doc-org.freedesktop.portal.ScreenCast.rst + doc-org.freedesktop.portal.Screenshot.rst + doc-org.freedesktop.portal.Secret.rst + doc-org.freedesktop.portal.Session.rst + doc-org.freedesktop.portal.Settings.rst + doc-org.freedesktop.portal.Trash.rst + doc-org.freedesktop.portal.Usb.rst + doc-org.freedesktop.portal.Wallpaper.rst diff --git a/doc/architecture.rst b/doc/architecture.rst new file mode 100644 index 0000000..e18c95d --- /dev/null +++ b/doc/architecture.rst @@ -0,0 +1,10 @@ +Architecture +============ + +The pages under this section document the internal architecture of various parts +of XDG Desktop Portal. + +.. toctree:: + :maxdepth: 1 + + documents-and-fuse \ No newline at end of file diff --git a/doc/building-and-running.rst b/doc/building-and-running.rst new file mode 100644 index 0000000..216e769 --- /dev/null +++ b/doc/building-and-running.rst @@ -0,0 +1,89 @@ +Building & Running +================== + +Normally, XDG Desktop Portal runs as a user session service, initialized on +demand through D-Bus activation. It usually starts with the session though, +as many desktop environments try to talk to XDG Desktop Portal on startup. +XDG Desktop Portal initializes specific backends through D-Bus activation +as well. + + +Building +-------- + +To build XDG Desktop Portal, first make sure you have the build dependencies +installed through your distribution's package manager. With them installed, +run: + +.. code-block:: shell + + meson setup . _build + meson compile -C _build + +Some distributions install portal configuration files in ``/usr``, while Meson +defaults to the prefix ``/usr/local``. If the portal configuration files in your +distribution are in ``/usr/share/xdg-desktop-portal/portals``, re-configure +Meson using ``meson setup --reconfigure . _build --prefix /usr`` and compile +again. + + +Running +------- + +XDG Desktop Portal needs to own the D-Bus name and replace the user session +service that might already be running. To do so, run: + +.. code-block:: shell + + _build/src/xdg-desktop-portal --replace + +If you are developing inside a ``toolbox`` container, you must use +``flatpak-spawn`` from ``flatpak-xdg-utils`` to run the service: + +.. code-block:: shell + + flatpak-spawn --host _build/src/xdg-desktop-portal --replace + +You may need to restart backends after replacing XDG Desktop Portal (please +replace ``[name]`` with the backend name, e.g. ``gnome`` or ``kde`` or ``wlr``): + +.. code-block:: shell + + systemctl --user restart xdg-desktop-portal-[name].service + +Testing +------- + +To execute the test suite present in XDG Desktop Portal, run: + +.. code-block:: shell + + meson test -C _build + +Enable the pre-commit hooks to catch issues early: + +.. code-block:: shell + + git config --local core.hooksPath .githooks/ + +When a branch is pushed to github, CI will also run tests in a few more +configurations. + +Documentation +------------- + +These instructions are for Fedora, where you will need these packages: + +.. code-block:: + + sudo dnf install json-glib-devel fuse3-devel gdk-pixbuf2-devel pipewire-devel python3-sphinx flatpak-devel python3-furo python-sphinxext-opengraph python-sphinx-copybutton + +Then you can build the website with: + +.. code-block:: shell + + meson setup . _build -Ddocumentation=enabled + ninja -C _build + +Then just load the build website into a browser of your choice from +``_build/doc/html/index.html`` \ No newline at end of file diff --git a/doc/common-conventions.rst b/doc/common-conventions.rst new file mode 100644 index 0000000..afa6ed3 --- /dev/null +++ b/doc/common-conventions.rst @@ -0,0 +1,58 @@ +Common Conventions +================== + +XDG Desktop Portal uses D-Bus in a slightly uncommon way, due to the potentially +long-running nature of some of its requests. These different usage patterns are +documented in the pages below: + +.. toctree:: + :hidden: + + terminology + requests + sessions + window-identifiers + icons + +.. cssclass:: tiled-toc + +* .. image:: _static/img/tiles/Terminology-l.png + :target: terminology.html + :class: only-light + .. image:: _static/img/tiles/Terminology-d.png + :target: terminology.html + :class: only-dark + + :doc:`Terminology ` +* .. image:: _static/img/tiles/Request-l.png + :target: requests.html + :class: only-light + .. image:: _static/img/tiles/Request-d.png + :target: requests.html + :class: only-dark + + :doc:`Requests ` +* .. image:: _static/img/tiles/Sessions-l.png + :target: sessions.html + :class: only-light + .. image:: _static/img/tiles/Sessions-d.png + :target: sessions.html + :class: only-dark + + :doc:`Sessions ` +* .. image:: _static/img/tiles/Window-ids-l.png + :target: window-identifiers.html + :class: only-light + .. image:: _static/img/tiles/Window-ids-d.png + :target: window-identifiers.html + :class: only-dark + + :doc:`Window Identifiers ` +* .. image:: _static/img/tiles/Icons-l.png + :target: icons.html + :class: only-light + .. image:: _static/img/tiles/Icons-d.png + :target: icons.html + :class: only-dark + + :doc:`Icons ` \ No newline at end of file diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..0a059aa --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,39 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "XDG Desktop Portal" +copyright = "2023-2025, XDG Desktop Portal authors" +author = "XDG Desktop Portal authors" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration +html_favicon = "favicon.ico" + +extensions = [ + "sphinxext.opengraph", + "sphinx_copybutton", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "furo" +# add custom files that are stored in _static +html_css_files = ["xdg.css"] +html_static_path = ["_static"] + +# -- Options for OpenGraph --------------------------------------------------- + +ogp_site_url = "https://flatpak.github.io/xdg-desktop-portal/docs/" +ogp_image = "_static/card.png" + +html_permalinks_icon = "🔗" diff --git a/doc/configuration-file.rst b/doc/configuration-file.rst new file mode 100644 index 0000000..a9be8c3 --- /dev/null +++ b/doc/configuration-file.rst @@ -0,0 +1,38 @@ +Configuration File +================== + +Portal backends are selected and can be configured by using one or more +configuration files. + +Desktop systems may have multiple desktop environments and portal backends +installed in parallel, and automatically picking portal backends in this +situation has proven to be a challenge. + +For this reason, XDG Desktop Portal uses a config-based matching system. +Usually, the configuration files are provided by the desktop environments +themselves, so that e.g. the GNOME-specific portal backends are picked in +GNOME sessions. No end user intervention is necessary in this case. + +Here's an example of a config file distributed by GNOME: + +.. code-block:: + + [preferred] + default=gnome;gtk; + org.freedesktop.impl.portal.Access=gnome-shell;gtk; + org.freedesktop.impl.portal.Secret=gnome-keyring; + +This file specifies that, by default, the ``gnome`` and ``gtk`` portal backends +must be used. However, specifically for :doc:`org.freedesktop.impl.portal.Access +`, the ``gnome-shell`` and ``gtk`` +portal backends must be used; and for :doc:`org.freedesktop.impl.portal.Secret +`, the ``gnome-keyring`` portal +backend must be used. + +You can read more about the config file syntax, install locations, and more, in +the portals.conf man page: + +.. toctree:: + :maxdepth: 1 + + portals.conf \ No newline at end of file diff --git a/doc/convenience-libraries.rst b/doc/convenience-libraries.rst new file mode 100644 index 0000000..d23cda6 --- /dev/null +++ b/doc/convenience-libraries.rst @@ -0,0 +1,19 @@ +Convenience Libraries +===================== + +Using the XDG Portals D-Bus APIs directly is often difficult and error-prone. +Fortunately, there are convenience libraries available that significantly ease +the development of apps: + +* `ASHPD `_: a **Rust** crate that + provides the APIs to interact with portals in idiomatic Rust. It has support for + GTK4, direct X11 windows, and direct Wayland surfaces. +* `libportal `_: small **C** library that + provides a GObject API to interact with portals. It provides language bindings + to a variety of other languages, such as **Python**, **JavaScript**, **Vala**, + and more. It has support for GTK3, GTK4, Qt 5, and Qt 6. +* `portal `_: a **Go** module that provides + native APIs for interacting with portals from idiomatic Go code. + It aims to be both toolkit agnostic and easy to use. +* `xdg_desktop_portal `_: a native + **Dart** package to interact with portals in **Dart** and **Flutter**. diff --git a/doc/copy-subdir.py b/doc/copy-subdir.py new file mode 100644 index 0000000..8cb64f5 --- /dev/null +++ b/doc/copy-subdir.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import os +import sys +import shutil + +subdir = os.environ["MESON_SUBDIR"] + +input_path = os.path.join(os.environ["MESON_SOURCE_ROOT"], subdir, sys.argv[1]) +output_path = os.path.join(os.environ["MESON_BUILD_ROOT"], subdir, sys.argv[2]) + +os.makedirs(os.path.dirname(output_path), exist_ok=True) + +shutil.copyfile(input_path, output_path) diff --git a/doc/copy-subtree.py b/doc/copy-subtree.py new file mode 100644 index 0000000..5777d97 --- /dev/null +++ b/doc/copy-subtree.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import os +import sys +import shutil + +subdir = os.environ["MESON_SUBDIR"] + +input_path = os.path.join(os.environ["MESON_SOURCE_ROOT"], subdir, sys.argv[1]) +output_path = os.path.join(os.environ["MESON_BUILD_ROOT"], subdir, sys.argv[2]) + +os.makedirs(os.path.dirname(output_path), exist_ok=True) + +shutil.copytree(input_path, output_path) diff --git a/doc/docbook.css b/doc/docbook.css deleted file mode 100644 index a2ac431..0000000 --- a/doc/docbook.css +++ /dev/null @@ -1,105 +0,0 @@ -body { - font-family: sans-serif; - max-width: 850px; - margin: auto; - overflow-wrap: break-word; -} -html { - background-color: #fafafa; -} -h1.title { -} -.permission { - color: #ee0000; - text-decoration: underline; -} -.synopsis, -.classsynopsis { - background: #eeeeee; -} -.programlisting { - background: #eeeeff; -} -.synopsis, -.classsynopsis, -.programlisting { - padding: 1em; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.03), - 0 1px 3px 1px rgba(0, 0, 0, .07), - 0 2px 6px 2px rgba(0, 0, 0, .03); - overflow: auto; - border-radius: 12px; -} -div.variablelist { - padding: 4px; - margin-left: 1em; -} -dl.variablelist td:first-child { - vertical-align: top; -} -td.shortcuts { - color: #770000; - font-size: 80%; -} -div.refnamediv { - margin-top: 2em; -} -div.toc { - border: 2em; -} -a { - text-decoration: none; -} -a:hover { - text-decoration: underline; - color: #ff0000; -} - -div.table table { - border-collapse: collapse; - border-spacing: 0px; - border-style: solid; - border-color: #777777; - border-width: 1px; -} - -div.table table td, -div.table table th { - border-style: solid; - border-color: #777777; - border-width: 1px; - padding: 3px; - vertical-align: top; -} - -div.table table th { - background-color: #eeeeee; -} - -@media (prefers-color-scheme: dark) { - html { - background-color: #242424; - color: white; - } - - a { - color: #8886fa; - } - - a:hover { - color: #5a57fa; - } - - .synopsis, - .classsynopsis, - .programlisting { - background: rgba(255, 255, 255, .08); - } -} - -@media only screen and (max-width: 900px) { - .book { - margin: 1rem; - font-size: 90% - } -} diff --git a/doc/documents-and-fuse.rst b/doc/documents-and-fuse.rst new file mode 100644 index 0000000..934275d --- /dev/null +++ b/doc/documents-and-fuse.rst @@ -0,0 +1,87 @@ +Documents & FUSE +================ + +The Document portal exposes files (filesystem entries) to applications through +FUSE. + +Files are scoped in a domain operations are restricted with a domain. The FUSE +filesystem will proxy the file access by referencing the inodes and presenting +the files in a virtual filesystem. + +Much like with xdg-desktop-portal, to run the development version of +the Document portal, do: + +.. code-block:: shell + + _build/document-portal/xdg-document-portal --replace + +FUSE +---- + +The Document portal is a separate executable, ``xdg-document-portal``, and it +implements the virtual filesystem using FUSE. The filesystem is mounted in the +user runtime ``doc`` directory. It usually is ``/run/user/[UID]/doc``, where +``[UID]`` is the user id on the system. + +Inside the Flatpak sandbox, files for which the app has permission to access are +available at ``/run/flatpak/doc``. + +Like any FUSE filesystem, it exposes inodes through the FUSE API. + +Domains +""""""" + +Domains specify the scope of an inode and organize the documents into +a hierarchy. It helps compartimentalize which documents the +applications have access to. + +- ``XDP_DOMAIN_ROOT``: root domain. +- ``XDP_DOMAIN_BY_APP``: used to find app domains by app id. +- ``XDP_DOMAIN_APP``: contains the documents allowed for the app. +- ``XDP_DOMAIN_DOCUMENT``: a physical file or directory is a document + in the ``XDP_DOMAIN_DOCUMENT``. + +The first three are considered to be virtual, which mean they are not +backed by a physical inode. + +The Document portal defines a few globals to hold the inodes: + +- ``root_inode`` is an inode that holds the root domain. +- ``by_app_inode`` is the inode for the by app domain. +- ``physical_inodes`` is a hash table for the physical inodes, the key + is ``devino``. +- ``all_inodes`` is a hash table for all the inodes, the key is the + inode number. + +The domains are used to create the filesystem hierarchy presented by +the document portal through the FUSE filesystem. It looks like this: + +.. code-block:: + + / + ├─ by-app + │ ├─ org.foo.Bar + │ │ ├─ doc1 + │ │ └─ doc3 + │ └─ org.bar.Foo + │ ├─ doc2 + │ └─ doc3 + ├─ doc1 + ├─ doc2 + └─ doc3 + +``/`` is the root domain. ``by-app`` is the ``XDP_DOMAIN_BY_APP`` domain. +``org.foo.Bar`` and ``org.bar.Foo`` are application ids. + +``doc1``, ``doc2``, ``doc3`` are unique identifiers for the documents. As +mentioned, they can be shared across apps. + +Document identifiers are unique. They are created using the inode number of the +physical file or directory. + +Custom xattrs +------------- + +Files and folders mounted through the Document portal have a custom attribute +with the host system path: ``user.document-portal.host-path``. This attribute +is read-only and any attempt to modify it will result in an error. \ No newline at end of file diff --git a/doc/favicon.ico b/doc/favicon.ico new file mode 100644 index 0000000..158a964 Binary files /dev/null and b/doc/favicon.ico differ diff --git a/doc/fix-rst-dbus.py b/doc/fix-rst-dbus.py new file mode 100755 index 0000000..cc7153d --- /dev/null +++ b/doc/fix-rst-dbus.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# This script massages the reStructuredText files generated by +# gdbus-codegen into something that's slightly more suitable for +# the online documentation of XDG Desktop Portal. It's not a +# general purpose script. + +import os +import sys + +output_dir = sys.argv[1] +filename_prefix = sys.argv[2] +inputs = sys.argv[3:] + + +def adjust_title(lines): + title = lines[3].strip() + + if title.startswith("org.freedesktop.portal."): + adjusted_title = title.replace("org.freedesktop.portal.", "") + elif title.startswith("org.freedesktop.impl.portal"): + adjusted_title = title.replace("org.freedesktop.impl.portal.", "") + elif title.startswith("org.freedesktop.host.portal"): + adjusted_title = title.replace("org.freedesktop.host.portal.", "") + elif title.startswith("org.freedesktop.background.Monitor"): + adjusted_title = title.replace( + "org.freedesktop.background.Monitor", "Background Apps Monitor" + ) + else: + adjusted_title = title + + # CamelCase → Camel Case + if adjusted_title not in ["OpenURI", "ScreenCast"]: + adjusted_title = "".join( + map(lambda x: x if x.islower() else f" {x}", adjusted_title) + ) + + lines[3] = f"{adjusted_title}\n" + + +# Temporary fix for '.. {title}:' strings in the generated files. Should be +# removed after GLib 2.78.4 hits the CI images. +# +# See: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3751 +def fix_title_template_string(lines): + for index, line in enumerate(lines): + if line.strip() == ".. _{title}:": + next_title = lines[index + 2].strip() + lines[index] = f".. _{next_title}:\n" + + +for file in inputs: + basename = os.path.basename(file) + fullpath = os.path.join(output_dir, f"{filename_prefix}-{basename}") + + with open(fullpath) as f: + lines = f.readlines() + + adjust_title(lines) + fix_title_template_string(lines) + + with open(fullpath, "w") as f: + f.writelines(lines) diff --git a/doc/for-app-developers.rst b/doc/for-app-developers.rst new file mode 100644 index 0000000..bf106db --- /dev/null +++ b/doc/for-app-developers.rst @@ -0,0 +1,50 @@ +For App Developers +================== + +XDG Desktop Portal is a session service that provides D-Bus interfaces for apps +to interact with the desktop. + +Portal interfaces can be used by sandboxed and unsandboxed apps alike, but +sandboxed apps benefit the most since they don't need special permissions to use +portal APIs. XDG Desktop Portal safeguards many resources and features with a +user-controlled permission system. + +The primary goal of portals is to expose common functionality and integration +with the desktop without requiring apps to write desktop-specific code, or +loosen their sandbox restrictions. + +.. toctree:: + :hidden: + + reasons-to-use-portals + convenience-libraries + api-reference + +.. cssclass:: tiled-toc + +* .. image:: _static/img/tiles/Reasons-l.png + :target: reasons-to-use-portals.html + :class: only-light + .. image:: _static/img/tiles/Reasons-d.png + :target: reasons-to-use-portals.html + :class: only-dark + + :doc:`Reasons to Use Portals ` + +* .. image:: _static/img/tiles/Libraries-l.png + :target: convenience-libraries.html + :class: only-light + .. image:: _static/img/tiles/Libraries-d.png + :target: convenience-libraries.html + :class: only-dark + + :doc:`Convenience Libraries ` + +* .. image:: _static/img/tiles/APIs-l.png + :target: api-reference.html + :class: only-light + .. image:: _static/img/tiles/APIs-d.png + :target: api-reference.html + :class: only-dark + + :doc:`API Reference ` \ No newline at end of file diff --git a/doc/for-contributors.rst b/doc/for-contributors.rst new file mode 100644 index 0000000..782c317 --- /dev/null +++ b/doc/for-contributors.rst @@ -0,0 +1,48 @@ +For Contributors +================ + +Thanks for your interest in contributing to XDG Desktop Portal! + +XDG Desktop Portal is a D-Bus oriented, security-sensitive service, +as it mediates the interactions between sandboxed apps and the host +system. + +In the sections below you will find information about how to start +contributing to XDG Desktop Portal, the expected coding and code +submission practices, as well as internal documentation about +more complex parts of XDG Desktop Portal. + +.. toctree:: + :hidden: + + building-and-running + pull-requests + architecture + +.. cssclass:: tiled-toc + +* .. image:: _static/img/tiles/Building-running-l.png + :target: building-and-running.html + :class: only-light + .. image:: _static/img/tiles/Building-running-d.png + :target: building-and-running.html + :class: only-dark + + :doc:`Building and Running ` + +* .. image:: _static/img/tiles/Pull-request-l.png + :target: pull-requests.html + :class: only-light + .. image:: _static/img/tiles/Pull-request-d.png + :target: pull-requests.html + :class: only-dark + + :doc:`Pull Requests ` +* .. image:: _static/img/tiles/Architecture-l.png + :target: architecture.html + :class: only-light + .. image:: _static/img/tiles/Architecture-d.png + :target: architecture.html + :class: only-dark + + :doc:`Architecture ` \ No newline at end of file diff --git a/doc/for-desktop-developers.rst b/doc/for-desktop-developers.rst new file mode 100644 index 0000000..9735ccc --- /dev/null +++ b/doc/for-desktop-developers.rst @@ -0,0 +1,102 @@ +For Desktop Developers +====================== + +The separation of the portal infrastructure into frontend and backend is a clean +way to provide suitable user interfaces that fit into different desktop +environments, while sharing the portal frontend. + +The portal backends are focused on providing user interfaces and accessing +session- or host-specific APIs and resources. Details of interacting with the +containment infrastructure such as checking access, registering files in the +Document portal, etc., are handled by the portal frontend. + +Portal backends can be layered together. For example, in a GNOME session, most +portal backend interfaces are implemented by the GNOME portal backend, but +the :doc:`org.freedesktop.impl.portal.Access ` +interface is implemented by GNOME Shell. + + +.. toctree:: + :hidden: + + writing-a-new-backend + configuration-file + system-integration + +.. cssclass:: tiled-toc + +* .. image:: _static/img/tiles/Backend-l.png + :target: writing-a-new-backend.html + :class: only-light + .. image:: _static/img/tiles/Backend-d.png + :target: writing-a-new-backend.html + :class: only-dark + + :doc:`Writing a New Backend ` +* .. image:: _static/img/tiles/Config-l.png + :target: configuration-file.html + :class: only-light + .. image:: _static/img/tiles/Config-d.png + :target: configuration-file.html + :class: only-dark + + :doc:`Configuration File ` + +* .. image:: _static/img/tiles/System-integration-l.png + :target: system-integration.html + :class: only-light + .. image:: _static/img/tiles/System-integration-d.png + :target: system-integration.html + :class: only-dark + + :doc:`System Integration ` + +D-Bus Interfaces +---------------- + +Portal backends must implement one or more backend D-Bus interfaces. The list of +D-Bus interfaces can be found below: + +.. toctree:: + :hidden: + + impl-dbus-interfaces + +.. cssclass:: tiled-toc + +* .. image:: _static/img/tiles/Dbus-l.png + :target: impl-dbus-interfaces.html + :class: only-light + .. image:: _static/img/tiles/Dbus-d.png + :target: impl-dbus-interfaces.html + :class: only-dark + + :doc:`Backend D-BUS Interfaces ` + +Background Apps Monitor +----------------------- + +In addition to managing the regular interfaces that sandboxed applications +use to interfact with the host system, XDG Desktop Portal also monitors +running applications without an active window - if the portal backend +provides an implementation of the Background portal. + +This API can be used by host system services to provide rich interfaces to +manage background running applications. + + +.. toctree:: + :hidden: + + doc-org.freedesktop.background.Monitor.rst + +.. cssclass:: tiled-toc + +* .. image:: _static/img/tiles/Bmon-l.png + :target: doc-org.freedesktop.background.Monitor.html + :class: only-light + .. image:: _static/img/tiles/Bmon-d.png + :target: doc-org.freedesktop.background.Monitor.html + :class: only-dark + + :doc:`Background Apps Monitor ` \ No newline at end of file diff --git a/doc/icons.rst b/doc/icons.rst new file mode 100644 index 0000000..c1f1648 --- /dev/null +++ b/doc/icons.rst @@ -0,0 +1,14 @@ +Icons +======== + +Some portal APIs accept Icon image data either as bytes or memfd sealed file descriptors. +These icons must pass validation by ``xdg-desktop-portal-validate-icon`` in order to be used +successfully. The requirements to pass validation are: + +.. csv-table:: Icon Requirements + :header: "Icon Property", "Requirement", "Description" + + "Shape", "Square", "All icons, whether PNG, JPEG, or SVG, must be square." + "Edge Length", "512 pixels", "For raster images, the maximum edge length is 512 pixels." + "SVG File Size", "4096 bytes", "For SVG images, the data describing the SVG must fit within 4096 bytes." + "Raster File Size", "4 MiB", "For raster images, the total data must fit within 4MB." diff --git a/doc/img/tiles/APIs-d.png b/doc/img/tiles/APIs-d.png new file mode 100644 index 0000000..6a8b498 Binary files /dev/null and b/doc/img/tiles/APIs-d.png differ diff --git a/doc/img/tiles/APIs-l.png b/doc/img/tiles/APIs-l.png new file mode 100644 index 0000000..9621b32 Binary files /dev/null and b/doc/img/tiles/APIs-l.png differ diff --git a/doc/img/tiles/Architecture-d.png b/doc/img/tiles/Architecture-d.png new file mode 100644 index 0000000..c6f0529 Binary files /dev/null and b/doc/img/tiles/Architecture-d.png differ diff --git a/doc/img/tiles/Architecture-l.png b/doc/img/tiles/Architecture-l.png new file mode 100644 index 0000000..631d1b3 Binary files /dev/null and b/doc/img/tiles/Architecture-l.png differ diff --git a/doc/img/tiles/Backend-d.png b/doc/img/tiles/Backend-d.png new file mode 100644 index 0000000..b101c70 Binary files /dev/null and b/doc/img/tiles/Backend-d.png differ diff --git a/doc/img/tiles/Backend-l.png b/doc/img/tiles/Backend-l.png new file mode 100644 index 0000000..b6a0980 Binary files /dev/null and b/doc/img/tiles/Backend-l.png differ diff --git a/doc/img/tiles/Bmon-d.png b/doc/img/tiles/Bmon-d.png new file mode 100644 index 0000000..d677b10 Binary files /dev/null and b/doc/img/tiles/Bmon-d.png differ diff --git a/doc/img/tiles/Bmon-l.png b/doc/img/tiles/Bmon-l.png new file mode 100644 index 0000000..b24f17b Binary files /dev/null and b/doc/img/tiles/Bmon-l.png differ diff --git a/doc/img/tiles/Building-running-d.png b/doc/img/tiles/Building-running-d.png new file mode 100644 index 0000000..9878066 Binary files /dev/null and b/doc/img/tiles/Building-running-d.png differ diff --git a/doc/img/tiles/Building-running-l.png b/doc/img/tiles/Building-running-l.png new file mode 100644 index 0000000..24b18be Binary files /dev/null and b/doc/img/tiles/Building-running-l.png differ diff --git a/doc/img/tiles/Config-d.png b/doc/img/tiles/Config-d.png new file mode 100644 index 0000000..9927031 Binary files /dev/null and b/doc/img/tiles/Config-d.png differ diff --git a/doc/img/tiles/Config-l.png b/doc/img/tiles/Config-l.png new file mode 100644 index 0000000..8efc292 Binary files /dev/null and b/doc/img/tiles/Config-l.png differ diff --git a/doc/img/tiles/Dbus-d.png b/doc/img/tiles/Dbus-d.png new file mode 100644 index 0000000..7880d05 Binary files /dev/null and b/doc/img/tiles/Dbus-d.png differ diff --git a/doc/img/tiles/Dbus-l.png b/doc/img/tiles/Dbus-l.png new file mode 100644 index 0000000..f170cad Binary files /dev/null and b/doc/img/tiles/Dbus-l.png differ diff --git a/doc/img/tiles/Icons-d.png b/doc/img/tiles/Icons-d.png new file mode 100644 index 0000000..e3c3b47 Binary files /dev/null and b/doc/img/tiles/Icons-d.png differ diff --git a/doc/img/tiles/Icons-l.png b/doc/img/tiles/Icons-l.png new file mode 100644 index 0000000..9163640 Binary files /dev/null and b/doc/img/tiles/Icons-l.png differ diff --git a/doc/img/tiles/Libraries-d.png b/doc/img/tiles/Libraries-d.png new file mode 100644 index 0000000..6c7a0cf Binary files /dev/null and b/doc/img/tiles/Libraries-d.png differ diff --git a/doc/img/tiles/Libraries-l.png b/doc/img/tiles/Libraries-l.png new file mode 100644 index 0000000..aae5ab7 Binary files /dev/null and b/doc/img/tiles/Libraries-l.png differ diff --git a/doc/img/tiles/Pull-request-d.png b/doc/img/tiles/Pull-request-d.png new file mode 100644 index 0000000..3af0faa Binary files /dev/null and b/doc/img/tiles/Pull-request-d.png differ diff --git a/doc/img/tiles/Pull-request-l.png b/doc/img/tiles/Pull-request-l.png new file mode 100644 index 0000000..39686e2 Binary files /dev/null and b/doc/img/tiles/Pull-request-l.png differ diff --git a/doc/img/tiles/Reasons-d.png b/doc/img/tiles/Reasons-d.png new file mode 100644 index 0000000..afd29cf Binary files /dev/null and b/doc/img/tiles/Reasons-d.png differ diff --git a/doc/img/tiles/Reasons-l.png b/doc/img/tiles/Reasons-l.png new file mode 100644 index 0000000..e6e648a Binary files /dev/null and b/doc/img/tiles/Reasons-l.png differ diff --git a/doc/img/tiles/Request-d.png b/doc/img/tiles/Request-d.png new file mode 100644 index 0000000..ebe9acf Binary files /dev/null and b/doc/img/tiles/Request-d.png differ diff --git a/doc/img/tiles/Request-l.png b/doc/img/tiles/Request-l.png new file mode 100644 index 0000000..b643e9d Binary files /dev/null and b/doc/img/tiles/Request-l.png differ diff --git a/doc/img/tiles/Sessions-d.png b/doc/img/tiles/Sessions-d.png new file mode 100644 index 0000000..cd2b4cd Binary files /dev/null and b/doc/img/tiles/Sessions-d.png differ diff --git a/doc/img/tiles/Sessions-l.png b/doc/img/tiles/Sessions-l.png new file mode 100644 index 0000000..301d18e Binary files /dev/null and b/doc/img/tiles/Sessions-l.png differ diff --git a/doc/img/tiles/System-integration-d.png b/doc/img/tiles/System-integration-d.png new file mode 100644 index 0000000..2c5053b Binary files /dev/null and b/doc/img/tiles/System-integration-d.png differ diff --git a/doc/img/tiles/System-integration-l.png b/doc/img/tiles/System-integration-l.png new file mode 100644 index 0000000..ad73c7e Binary files /dev/null and b/doc/img/tiles/System-integration-l.png differ diff --git a/doc/img/tiles/Terminology-d.png b/doc/img/tiles/Terminology-d.png new file mode 100644 index 0000000..6c879a6 Binary files /dev/null and b/doc/img/tiles/Terminology-d.png differ diff --git a/doc/img/tiles/Terminology-l.png b/doc/img/tiles/Terminology-l.png new file mode 100644 index 0000000..9a389b6 Binary files /dev/null and b/doc/img/tiles/Terminology-l.png differ diff --git a/doc/img/tiles/Window-ids-d.png b/doc/img/tiles/Window-ids-d.png new file mode 100644 index 0000000..a5860c7 Binary files /dev/null and b/doc/img/tiles/Window-ids-d.png differ diff --git a/doc/img/tiles/Window-ids-l.png b/doc/img/tiles/Window-ids-l.png new file mode 100644 index 0000000..de82e18 Binary files /dev/null and b/doc/img/tiles/Window-ids-l.png differ diff --git a/doc/impl-dbus-interfaces.rst b/doc/impl-dbus-interfaces.rst new file mode 100644 index 0000000..937d2d1 --- /dev/null +++ b/doc/impl-dbus-interfaces.rst @@ -0,0 +1,34 @@ +Backend D-Bus Interfaces +======================== + +The backend interfaces are used by the portal frontend to carry out portal +requests. They are provided by a separate process (or processes), and are not +accessible to sandboxed applications. + +.. toctree:: + :maxdepth: 1 + + doc-org.freedesktop.impl.portal.Access.rst + doc-org.freedesktop.impl.portal.Account.rst + doc-org.freedesktop.impl.portal.AppChooser.rst + doc-org.freedesktop.impl.portal.Background.rst + doc-org.freedesktop.impl.portal.Clipboard.rst + doc-org.freedesktop.impl.portal.DynamicLauncher.rst + doc-org.freedesktop.impl.portal.Email.rst + doc-org.freedesktop.impl.portal.FileChooser.rst + doc-org.freedesktop.impl.portal.GlobalShortcuts.rst + doc-org.freedesktop.impl.portal.Inhibit.rst + doc-org.freedesktop.impl.portal.InputCapture.rst + doc-org.freedesktop.impl.portal.Lockdown.rst + doc-org.freedesktop.impl.portal.Notification.rst + doc-org.freedesktop.impl.portal.PermissionStore.rst + doc-org.freedesktop.impl.portal.Print.rst + doc-org.freedesktop.impl.portal.RemoteDesktop.rst + doc-org.freedesktop.impl.portal.Request.rst + doc-org.freedesktop.impl.portal.ScreenCast.rst + doc-org.freedesktop.impl.portal.Screenshot.rst + doc-org.freedesktop.impl.portal.Secret.rst + doc-org.freedesktop.impl.portal.Session.rst + doc-org.freedesktop.impl.portal.Settings.rst + doc-org.freedesktop.impl.portal.Usb.rst + doc-org.freedesktop.impl.portal.Wallpaper.rst diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..2c36e79 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,47 @@ +.. XDG Desktop Portals documentation master file, created by + sphinx-quickstart on Thu Aug 24 16:58:13 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. image:: _static/xdg-portal-light.png + :class: only-light +.. image:: _static/xdg-portal-dark.png + :class: only-dark + +XDG Desktop Portal +================== + +XDG Desktop Portal allow `Flatpak apps `_, and other desktop +containment frameworks, to interact with the system in a secure and well defined way. + +This documentation covers everything you need to know to build apps that use portals, +write portal backends for your desktop environment, configure and distribute portals +as part of a distribution, as well as basic concepts and common conventions. + +The documentation pages target primarily app developers, desktop developers, and +system distributors and administrators. The contents may also be relevant to those +who have a general interest in portals. + +Content Overview +---------------- + +This documentation is made up of the following sections: + +* :doc:`Common conventions `: coding patterns and principles + common when **app and desktop developers** are working with portal APIs. +* :doc:`App Development `: portal APIs that **apps** can use + to interact with the host system. +* :doc:`Desktop Integration `: interfaces and + configuration files that **desktop developers** can implement and install in + order to write a portal backend. +* :doc:`Contributing `: how **contributors** can contribute to + the project. + +.. toctree:: + :maxdepth: 2 + :hidden: + + common-conventions + for-app-developers + for-desktop-developers + for-contributors diff --git a/doc/meson.build b/doc/meson.build index 97c5af6..d6eea32 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -1,42 +1,4 @@ -if build_docbook - xslfile = files('xmlto-config.xsl') - - portal_docs_xml = configure_file( - input: 'portal-docs.xml.in', - output: '@BASENAME@', - configuration: { - 'VERSION': meson.project_version(), - }, - ) - - docs = custom_target( - 'html', - build_by_default: true, - depends: [portal_built_sources, impl_built_sources], - input: portal_docs_xml, - output: 'portal-docs.html', - command: [xmlto, get_option('xmlto-flags'), 'xhtml-nochunks', - '-o', '@OUTDIR@', - '-m', xslfile, - '--searchpath', meson.project_build_root() / 'src', - '@INPUT@'], - install: true, - install_dir: docs_dir, - ) - doc_extra = files( - 'docbook.css', - 'redirect.html' - ) - foreach f : doc_extra - configure_file( - input: f, - output: '@PLAINNAME@', - copy: true, - install: true, - install_dir: docs_dir, - ) - endforeach -endif +manpages = [] if rst2man.found() manpage_conf = configuration_data() @@ -46,7 +8,7 @@ if rst2man.found() rst2man_flags = [ '--syntax-highlight=none', ] - + man_pages = [ { 'input': 'portals.conf.rst.in', 'output': 'portals.conf', 'section': '5' }, ] @@ -61,7 +23,7 @@ if rst2man.found() man_section = man_page.get('section', '1') man_full = '@0@.@1@'.format(man_output, man_section) - custom_target('man-' + man_output, + manpages += custom_target('man-' + man_output, input: man_input, output: man_full, command: [ rst2man, rst2man_flags, '@INPUT@' ], @@ -71,3 +33,116 @@ if rst2man.found() ) endforeach endif + +if build_documentation + # Gather the XML files under data + all_interfaces_xml = [] + foreach i: portal_sources + all_interfaces_xml += i + endforeach + foreach i: portal_host_sources + all_interfaces_xml += i + endforeach + foreach i: portal_impl_sources + all_interfaces_xml += i + endforeach + foreach i: background_monitor_sources + all_interfaces_xml += i + endforeach + + # Replace the extension for gdbus-codegen + all_interfaces_rst = [] + foreach i: all_interfaces_xml + f = fs.name(i) + all_interfaces_rst += fs.replace_suffix(f, '.rst') + endforeach + + interfaces_rst = custom_target('interfaces_rst', + input: all_interfaces_xml, + output: all_interfaces_rst, + command: [ gdbus_codegen, '--generate-rst', 'doc/doc', '@INPUT@' ], + ) + + fix_rst_dbus = find_program('fix-rst-dbus.py') + fixed_rst_files = custom_target('fix_rst_dbus', + input: interfaces_rst, + output: 'rst-dbus.fixed', + build_always_stale: true, + build_by_default: true, + command: [ fix_rst_dbus, meson.current_build_dir(), 'doc', '@INPUT@' ], + depends: interfaces_rst, + depend_files: all_interfaces_xml, + ) + + docs_sources = [ + 'conf.py', + 'favicon.ico', + + 'api-reference.rst', + 'architecture.rst', + 'building-and-running.rst', + 'common-conventions.rst', + 'configuration-file.rst', + 'convenience-libraries.rst', + 'documents-and-fuse.rst', + 'for-app-developers.rst', + 'for-contributors.rst', + 'for-desktop-developers.rst', + 'icons.rst', + 'impl-dbus-interfaces.rst', + 'index.rst', + 'pull-requests.rst', + 'reasons-to-use-portals.rst', + 'requests.rst', + 'terminology.rst', + 'sessions.rst', + 'system-integration.rst', + 'window-identifiers.rst', + 'writing-a-new-backend.rst', + ] + + copied_docs_sources = [] + foreach d: docs_sources + copied_docs_sources += configure_file(input: d, output: d, copy: true) + endforeach + + docs_inputs = copied_docs_sources + interfaces_rst + if rst2man.found() + docs_inputs += manpages + endif + + # Copy static files to the appropriate output subdir + copy = find_program('copy-subdir.py') + copytree = find_program('copy-subtree.py') + + static_files = [ + 'bullet.svg', + 'card.png', + 'inter.woff2', + 'toc_bullet.svg', + 'xdg.css', + 'xdg-portal-dark.png', + 'xdg-portal-light.png', + ] + + foreach f: static_files + run_command(copy, '_static' / f, '_static' / f) + endforeach + + run_command(copytree, 'img', '_static' / 'img') + + sphinx_command = [ sphinx_build ] + + if get_option('werror') + sphinx_command += [ '-W', '--keep-going' ] + endif + + custom_target('docs', + input: docs_inputs, + output: 'html', + command: sphinx_command + [ meson.current_build_dir(), '@OUTPUT@' ], + build_by_default: true, + depends: fixed_rst_files, + depend_files: all_interfaces_xml, + ) +endif diff --git a/doc/portal-docs.xml.in b/doc/portal-docs.xml.in deleted file mode 100644 index f83ac2f..0000000 --- a/doc/portal-docs.xml.in +++ /dev/null @@ -1,188 +0,0 @@ - - -]> - - - Portal Documentation - Version @VERSION@ - - - Common Conventions - - Requests made via portal interfaces generally involve user interaction, - and dialogs that can stay open for a long time. Therefore portal APIs - don't just use async method calls (which time out after at most 25 seconds), - but instead return results via a Response signal on Request objects. - - - Portal APIs don't use properties very much. This is partially because - we need to be careful about the flow of information, and partially because - it would be unexpected to have a dialog appear when you just set a property. - -
- Portal requests - - The general flow of the portal API is that the application makes - a portal request, the portal replies to that method call with a - handle (i.e. object path) to Request object that corresponds to the - request. The object is exported on the bus and stays alive as long - as the user interaction lasts. When the user interaction is over, - the portal sends a Response signal back to the application with - the results from the interaction, if any. - - - To avoid a race condition between the caller subscribing to the signal - after receiving the reply for the method call and the signal getting emitted, - a convention for Request object paths has been established that allows the - caller to subscribe to the signal before making the method call. - -
-
- Sessions - - Some portal requests are connected to each other and need to be used in - sequence. The pattern we use in such cases is a Session object. Just like - Requests, Sessions are represented by an object path, that is returned by - the initial CreateSession call. Subsequent calls take the object path of - the session they operate on as an argument. - - - Sessions can be ended from the application side by calling the Close() - method. They can also be closed from the service side, in which case the - ::Closed signal is emitted to inform the application. - -
-
- Parent window identifiers - - Most portals interact with the user by showing dialogs. These dialogs - should generally be placed on top of the application window that triggered - them. To arrange this, the compositor needs to know about the application - window. Many portal requests expect a "parent_window" string argument for - this reason. - - - Under X11, the "parent_window" argument should have the form - "x11:XID", where XID - is the XID of the application window in hexadecimal notation. - - - Under Wayland, it should have the form "wayland:HANDLE", - where HANDLE is a surface handle obtained with the - xdg_foreign protocol. - - - For other windowing systems, or if you don't have a suitable handle, just - pass an empty string for "parent_window". - -
-
- - Portal API Reference - - - Portal interfaces are available to sandboxed applications with the - default filtered session bus access of Flatpak. Unless otherwise - specified, they appear under the bus name org.freedesktop.portal.Desktop - and the object path /org/freedesktop/portal/desktop on the session - bus. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Portal Backend API Reference - - - The backend interfaces are used by the portal frontend to - carry out portal requests. They are provided by a separate process - (or processes), and are not accessible to sandboxed applications. - - - The separation of the portal infrastructure into frontend and backend - is a clean way to provide suitable user interfaces that fit into - different desktop environments, while sharing the portal frontend. - - - The portal backends are focused on providing user interfaces and - accessing session- or host-specific APIs and resources. Details of - interacting with the containment infrastructure such as checking - access, registering files in the document portal, etc., are handled - by the portal frontend. - - - - - - - - - - - - - - - - - - - - - - - - - - - Background Apps Monitoring API - - - In addition to managing the regular interfaces that sandboxed - applications use to interfact with the host system, xdg-desktop-portals - also monitors running applications without an active window - if the - portal backend provides an implementation of the Background portal. - - - This API can be used by host system services to provide rich interfaces - to manage background running applications. - - - - -
diff --git a/doc/portals.conf.rst.in b/doc/portals.conf.rst.in index 5b2a045..6ab702b 100644 --- a/doc/portals.conf.rst.in +++ b/doc/portals.conf.rst.in @@ -126,8 +126,8 @@ EXAMPLE [preferred] # Use xdg-desktop-portal-gtk for every portal interface... default=gtk - # ... except for the Screencast interface - org.freedesktop.impl.portal.Screencast=gnome + # ... except for the ScreenCast interface + org.freedesktop.impl.portal.ScreenCast=gnome ENVIRONMENT diff --git a/doc/pull-requests.rst b/doc/pull-requests.rst new file mode 100644 index 0000000..f8ad38a --- /dev/null +++ b/doc/pull-requests.rst @@ -0,0 +1,19 @@ +Pull Requests & Issues +====================== + +XDG Desktop Portal uses GitHub to +`discuss new portals and features `_, +`track issues `_ and +`take contributions `_. + +Please be kind and patient as code reviews can be long and be minutious, issue +triage takes effort and everyone has limited time and resources. + +Before developing features or fixing bugs, please make sure you have done the +following: + +- Your code is not on the ``main`` branch of your fork. +- The code has been tested. +- All commit messages are properly formatted and commits squashed where + appropriate. +- You have included updates to all appropriate documentation. \ No newline at end of file diff --git a/doc/reasons-to-use-portals.rst b/doc/reasons-to-use-portals.rst new file mode 100644 index 0000000..1b44886 --- /dev/null +++ b/doc/reasons-to-use-portals.rst @@ -0,0 +1,26 @@ +Reasons to Use Portals +====================== + +Using XDG Desktop Portal brings major advantages over writing desktop-specific +code for app developers: + +* **Strict sandbox**: portals enable sandboxed apps to access a curated set of + features from the desktop environment and the host system, without weakening + the sandbox of the app. +* **Unified code**: by using portal APIs, apps only need to write a single + desktop-agnostic code that runs on a variety of desktop environments. Portals + can be used by sandboxed and unsandboxed apps alike. App developers are + encouraged to use portal APIs even for unsandboxed apps. +* **Seamless integration**: portal backends provide cost-free integration with + the desktop. For example, simply using the :ref:`File Chooser portal` + will make apps use the file picker dialog native to the desktop environment + they're running on. +* **Permission system**: portals can restrict access to system resources through + a permission system. Permissions can be granted, revoked, and controlled + individually by the end user. This gives end users more control over apps. + +In addition to the reasons above, some desktop features are primarily - and +sometimes exclusively - available through portals. For example, the primary way +of capturing screens and windows on Wayland desktops is through the +:ref:`ScreenCast portal`, and some desktop +environments don't even expose other means to capture the screen or windows. \ No newline at end of file diff --git a/doc/requests.rst b/doc/requests.rst new file mode 100644 index 0000000..d831f07 --- /dev/null +++ b/doc/requests.rst @@ -0,0 +1,33 @@ +Requests +======== + +Requests made via portal interfaces generally involve user interaction, and +dialogs that can stay open for a long time. Therefore portal APIs don't just use +async method calls (which time out after at most 25 seconds), but instead return +results via a ``Response`` signal on :ref:`Request ` +objects. + +Portal APIs don't use properties very much. This is partially because we need to +be careful about the flow of information, and partially because it would be +unexpected to have a dialog appear when you just set a property. However, every +portal has at least one version property that specifies the maximum version +supported by xdg-desktop-portal. + +General Flow +------------ + +The general flow of the portal API is as follows: + +1. The application makes a portal request +2. The portal replies to that method call with a handle (i.e. object path) to a + :ref:`Request ` object that corresponds to the + request +3. The object is exported on the bus and stays alive as long as the user + interaction lasts +4. When the user interaction is over, the portal sends a ``Response`` signal back + to the application with the results from the interaction, if any. + +To avoid a race condition between the caller subscribing to the signal after +receiving the reply for the method call and the signal getting emitted, a +convention for Request object paths has been established that allows the caller +to subscribe to the signal before making the method call. \ No newline at end of file diff --git a/doc/sessions.rst b/doc/sessions.rst new file mode 100644 index 0000000..90e3092 --- /dev/null +++ b/doc/sessions.rst @@ -0,0 +1,13 @@ +Sessions +======== + +Some portal requests are connected to each other and need to be used in +sequence. The pattern used in such cases is a :ref:`Session ` +object. Just like :ref:`Request `\s, sessions are +represented by an object path, that is returned by the initial ``CreateSession`` +call of the respective portal. Subsequent calls take the object path of the +session they operate on as an argument. + +Sessions can be ended from the application side by calling the ``Close()`` method +on the session. They can also be closed from the service side, in which case the +``::Closed`` signal is emitted on the Session object to inform the application. \ No newline at end of file diff --git a/doc/system-integration.rst b/doc/system-integration.rst new file mode 100644 index 0000000..b231eeb --- /dev/null +++ b/doc/system-integration.rst @@ -0,0 +1,54 @@ +System Integration +================== + +D-Bus Activation Environment +---------------------------- + +XDG Desktop Portal and its portal backends are activatable D-Bus services. +This means that they inherit environment variables from the "activation +environment" maintained by either ``systemd --user`` (on systems that use +systemd) or ``dbus-daemon`` (on systems that do not). They do not inherit +environment variables from the GUI environment or from the shell, unless +some component of the overall system takes responsibility for taking the +necessary environment variables from the GUI environment and sending them +to ``systemd`` or ``dbus-daemon`` to be added to the activation environment. + +In integrated desktop environments such as GNOME and KDE Plasma, and in +OS distributions with a high level of integration, this should be done +automatically by desktop environment or OS infrastructure. + +Variables that might need to be propagated in this way include, but are +not limited to: + +- ``DISPLAY`` +- ``PATH`` +- ``WAYLAND_DISPLAY`` +- ``XAUTHORITY`` +- ``XDG_CURRENT_DESKTOP`` +- ``XDG_DATA_DIRS`` + +In environments that are assembled out of individual components by +the user, it is the user's responsibility to ensure that this system +integration has been done, for example by using +`dbus-update-activation-environment(1) +`_ +or `systemctl --user import-environment VAR… +`_. + +Desktop Environment Requirements +-------------------------------- + +The display manager or GUI environment is responsible for setting +``XDG_CURRENT_DESKTOP`` to an appropriate value. + +The GUI environment should provide a +:doc:`portal configuration file ` with a name based on its +``XDG_CURRENT_DESKTOP``, to select appropriate portal backends. + +The GUI environment should arrange for its required portal backend or +backends to be installed as dependencies (possibly as optional +dependencies, if it is packaged in a loosely-coupled operating system). + +In environments that are assembled out of individual components by +the user, it is the user's responsibility to ensure that this system +integration has been done. \ No newline at end of file diff --git a/doc/terminology.rst b/doc/terminology.rst new file mode 100644 index 0000000..43c8cf7 --- /dev/null +++ b/doc/terminology.rst @@ -0,0 +1,8 @@ +Terminology +=========== + +- **Frontend**: the XDG Desktop Portal service itself. This is what provides the + APIs that *apps* interact with. +- **Backend**: desktop-specific implementation of portal interfaces. This is + what provides the user interfaces that *users* interact with. +- **Host system**: the privileged, unsandboxed part of the system stack. \ No newline at end of file diff --git a/doc/usb-portal.md b/doc/usb-portal.md new file mode 100644 index 0000000..0c1d2fd --- /dev/null +++ b/doc/usb-portal.md @@ -0,0 +1,119 @@ +# USB portal + +The USB portal is the middleman between sandboxed apps, and the +devices connected and available to the host system. This is the first +version of the portal. + +This part was implemented by + +- Hubert Figuière +- Georges Basile Stavracas Neto + +funded by the STF (Sovereign Tech Fund) effort, based upon initial +work by + +- Ryan Gonzalez + +and with help from other contributors. + +## Device filtering + +Sandboxed apps must declare which USB devices they support ahead of +time. This information is read by the XDG Desktop Portal and used to +determine which USB devices will be exposed to requesting apps. On +Flatpak, these enumerable and hidden devices are set by the `--usb` +and `--nousb` arguments against `flatpak build-finish` and `flatpak +run`. Neither `--devices=all` nor `--device=usb` do influence the +portal. + +Hidding a device always take precedence over making them enumerable, +even when a blanket permission (`--usb=all`) is set. + +However out of the sandbox we assume all devices are allowed as there +is no restriction that can prevent seeing anything. + +Individual devices are assigned a unique identifier by the portal, +which is used for all further interactions. This unique identifier is +completely random and independent of the device. Permission checks are +in place to not allow apps to try and guess device ids without having +permission to access then. + +## Permissions + +There are 2 dynamic permissions managed by the USB portal in the +permission store: + +1. Blanket USB permission: per-app permission to use any methods of +the USB portal. Without this permission, apps must not be able to do +anything - enumerate, monitor, or acquire - with the USB portal.[^1] + +2. Specific device permission: per-app permission to acquire a +specific USB device, down to the serial number. + +## Enumerating devices + +There are 2 ways for apps to learn about devices: + +- Apps can call the EnumerateDevices() method, which gives a snapshot +of the current devices to the app. + +- Apps can create a device monitoring session with CreateSession() +which sends the list of available devices on creation, and also +notifies the app about connected and disconnected devices. + +Only devices that the app is allowed to see are reported in both +cases. + +The udev properties exposed by device enumeration is limited to a +well known subset of properties.[^2] + +## Device acquisition & release + +Once an app has determined which devices it wants to access, the app +can call the AcquireDevices() method. This method may prompt a dialog +for the user to allow or deny the app from accessing specific devices. + +If permission is granted, XDG Desktop Portal tries to open the device +file on the behalf of the requesting app, and pass down the file +descriptor to that file.[^3] + +The caller must then call FinishAcquireDevices() until it indicate it +is finished. It's only necessary to call it more than once if there +are too many file descriptors to return. This is a D-Bus +limitation. Check the `finished` return argument. + +### Using the file descriptor + +The file descriptors returned by the portal are meant to be used with +USB library you use. + +In the case of libusb 1.0.23 and later, use `libusb_wrap_sys_device()` +and pass the file descriptor as the `sys_dev` argument. It should be +noted that libusb must be compiled with udev support (it is the +default) in order to be able to work. Without this it looks for the +non existant `/dev/usb` that is not present. + +If you use libhidapi, you need to use the function +`hid_libusb_wrap_sys_device()` provided by libhidapi-usb. + +--- + +[^1]: Exceptionally, apps can release previously acquired devices, +even when this permission is disabled. This is so because we don't yet +have kernel-side USB revoking. With USB revoking in place, it would be +possible to hard-cut app access right when the app permission changes. + +[^2]: This patch uses a hardcoded list. There is no mechanism for apps +to influence which other udev properties are fetched. This approach is +open to suggestions - it may be necessary to expose more information +more liberally through the portal. The initial attempt has been made +to provide sensible information usefull for display in a user +interface. + +[^3]: This is clearly not ideal. The ideal approach is to go through +logind's TakeDevice() method. However, that will add significant +complexity to the portal, since this logind method can only be called +by the session controller (i.e. the only executable capable of calling +TakeControl() in the session - usually the compositor). This can and +probably should be implemented in a subsequent round of improvements +to the USB portal. diff --git a/doc/website/Gemfile.lock b/doc/website/Gemfile.lock index 0ce7bec..5bd2218 100644 --- a/doc/website/Gemfile.lock +++ b/doc/website/Gemfile.lock @@ -48,7 +48,7 @@ GEM rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) + rexml (3.3.9) rouge (3.26.1) safe_yaml (1.0.5) sassc (2.4.0) @@ -56,7 +56,7 @@ GEM terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (1.8.0) - webrick (1.8.1) + webrick (1.8.2) PLATFORMS x86_64-linux diff --git a/doc/website/README.md b/doc/website/README.md index fadfd96..37b838f 100644 --- a/doc/website/README.md +++ b/doc/website/README.md @@ -2,23 +2,7 @@ This is the website for the [XDG Desktop Portal project](https://github.com/flatpak/xdg-desktop-portal). -## Setup +## Development -The process of setting up the site locally consists of: - -- Install ruby [gem bundler](https://bundler.io/). On [Fedora](https://getfedora.org/)/in the [Toolbx](https://containertoolbx.org) you do: - -``` -toolbox enter -sudo dnf install rubygem-bundler -cd xdg-desktop-portal/doc/website -bundle install -``` - - -- Test the site locally: -``` -bundle exec jekyll s -``` - -- `git commit` your changes and create a merge request. After review, the merged changes get automaticaly deployed to the site. +The contribution guidelines have been moved to the online documentation: +https://flatpak.github.io/xdg-desktop-portal/docs/for-contributors.html diff --git a/doc/website/_includes/footer.html b/doc/website/_includes/footer.html index eacba4e..95cf1d1 100644 --- a/doc/website/_includes/footer.html +++ b/doc/website/_includes/footer.html @@ -1,5 +1,5 @@
-

© {{ site.title }}, 2023

+

© {{ site.title }}, 2023-2025

Website source

diff --git a/doc/website/_includes/head.html b/doc/website/_includes/head.html index 90fc0b2..da6c949 100644 --- a/doc/website/_includes/head.html +++ b/doc/website/_includes/head.html @@ -10,10 +10,14 @@ - + - + + + + + diff --git a/doc/website/android-chrome-192x192.png b/doc/website/android-chrome-192x192.png new file mode 100644 index 0000000..c7d6e45 Binary files /dev/null and b/doc/website/android-chrome-192x192.png differ diff --git a/doc/website/android-chrome-512x512.png b/doc/website/android-chrome-512x512.png new file mode 100644 index 0000000..d82c734 Binary files /dev/null and b/doc/website/android-chrome-512x512.png differ diff --git a/doc/website/apple-touch-icon.png b/doc/website/apple-touch-icon.png index 72fc757..829e065 100644 Binary files a/doc/website/apple-touch-icon.png and b/doc/website/apple-touch-icon.png differ diff --git a/doc/website/apple-touch-icon.svg b/doc/website/apple-touch-icon.svg deleted file mode 100644 index 9256868..0000000 --- a/doc/website/apple-touch-icon.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - xdg-portal - - - - - diff --git a/doc/website/favicon-16x16.png b/doc/website/favicon-16x16.png new file mode 100644 index 0000000..caca24d Binary files /dev/null and b/doc/website/favicon-16x16.png differ diff --git a/doc/website/favicon-32x32.png b/doc/website/favicon-32x32.png new file mode 100644 index 0000000..556f8aa Binary files /dev/null and b/doc/website/favicon-32x32.png differ diff --git a/doc/website/favicon.ico b/doc/website/favicon.ico new file mode 100644 index 0000000..158a964 Binary files /dev/null and b/doc/website/favicon.ico differ diff --git a/doc/website/favicon.png b/doc/website/favicon.png deleted file mode 100644 index c531fcd..0000000 Binary files a/doc/website/favicon.png and /dev/null differ diff --git a/doc/website/index.md b/doc/website/index.md index 0ef7330..9436d34 100644 --- a/doc/website/index.md +++ b/doc/website/index.md @@ -9,7 +9,7 @@ layout: default # XDG Desktop Portal -A portal frontend service for [Flatpak](http://www.flatpak.org) and other +A portal frontend service for [Flatpak](https://flatpak.org) and other desktop containment frameworks. xdg-desktop-portal works by exposing a series of D-Bus interfaces known as @@ -20,14 +20,14 @@ The portal interfaces include APIs for file access, opening URIs, printing and others. - + - + Documentation for the available D-Bus interfaces ## Version Numbering -xdg-desktop-portal uses even minor vesion numbers for stable versions, and odd +xdg-desktop-portal uses even minor version numbers for stable versions, and odd minor version numbers for unstable versions. During an unstable version cycle, portal APIs can make backward incompatible changes, meaning that applications should only depend on APIs defined in stable xdg-desktop-portal versions in @@ -60,6 +60,7 @@ Here are some examples of available backends: - wlroots [xdg-desktop-portal-wlr](https://github.com/emersion/xdg-desktop-portal-wlr) - Deepin [xdg-desktop-portal-dde](https://github.com/linuxdeepin/xdg-desktop-portal-dde) - Xapp (Cinnamon, MATE, Xfce) [xdg-desktop-portal-xapp](https://github.com/linuxmint/xdg-desktop-portal-xapp) +- COSMIC [xdg-desktop-portal-cosmic](https://github.com/pop-os/xdg-desktop-portal-cosmic) ## Design Considerations @@ -69,7 +70,7 @@ code: GTK dialogs for GNOME, Qt dialogs for KDE) - One of the limitations of the D-Bus proxying in flatpak is that allowing a sandboxed app to talk to a name implicitly also allows it to talk to any other - name owned by the same unique name. Therefore, sandbox-facing D-Bus apis + name owned by the same unique name. Therefore, sandbox-facing D-Bus APIs should generally be hosted on a dedicated bus connection. For portals, the frontend takes care of this for us. - The frontend can handle all the interaction with _portal infrastructure_, such @@ -82,7 +83,7 @@ The portal apis are all following the pattern of an initial method call, whose response returns an object handle for an _org.freedesktop.portal.Request_ object that represents the portal interaction. The end of the interaction is done via a _Response_ signal that gets emitted on that object. This pattern was chosen over -a simple method call with return, since portal apis are expected to show dialogs +a simple method call with return, since portal APIs are expected to show dialogs and interact with the user, which may well take longer than the maximum method call timeout of D-Bus. Another advantage is that the caller can cancel an ongoing interaction by calling the _Cancel_ method on the request object. @@ -92,7 +93,7 @@ One consideration for deciding the shape of portal APIs is that we want them to possible to have apps use them _transparently_. For example, the OpenFile portal is working well as a backend for the GtkFileChooserNative API. -When it comes to files, we need to be careful to not let portal apis subvert the +When it comes to files, we need to be careful to not let portal APIs subvert the limited filesystem view that apps have in their sandbox. Therefore, files should only be passed into portal APIs in one of two forms: - As a document ID referring to a file that has been exported in the document @@ -101,8 +102,8 @@ only be passed into portal APIs in one of two forms: and passing an fd proves that the app inside the sandbox has access to the file to open it. -When it comes to processes, passing pids around is not useful in a sandboxed -world where apps are likely in their own pid namespace. And passing pids from +When it comes to processes, passing PIDs around is not useful in a sandboxed +world where apps are likely in their own PID namespace. And passing PIDs from inside the sandbox is problematic, since the app can just lie. ## Contributing diff --git a/doc/website/safari-pinned-tab.svg b/doc/website/safari-pinned-tab.svg new file mode 100644 index 0000000..d3a4714 --- /dev/null +++ b/doc/website/safari-pinned-tab.svg @@ -0,0 +1,47 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/doc/website/site.webmanifest b/doc/website/site.webmanifest new file mode 100644 index 0000000..286e44f --- /dev/null +++ b/doc/website/site.webmanifest @@ -0,0 +1,18 @@ +{ + "name": "XDG Desktop Portal", + "short_name": "XDG Desktop Portal", + "icons": [ + { + "src": "android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#F6D32D", + "background_color": "#F6D32D" +} \ No newline at end of file diff --git a/doc/window-identifiers.rst b/doc/window-identifiers.rst new file mode 100644 index 0000000..fd9a16b --- /dev/null +++ b/doc/window-identifiers.rst @@ -0,0 +1,19 @@ +Window Identifiers +================== + +Most portals interact with the user by showing dialogs. These dialogs should +generally be placed on top of the application window that triggered them. To +arrange this, the compositor needs to know about the application window. Many +portal requests expect a ``"parent_window"`` string argument for this reason. + +Under X11, the ``"parent_window"`` argument should have the form ``x11:``, +where ```` is the XID of the application window in hexadecimal notation. +For example, ``x11:1234``. + +Under Wayland, it should have the form ``wayland:``, where ```` +is a surface handle obtained with the `xdg_foreign +`_ +protocol. For example, ``wayland:~12l9jdl.-a``. + +For other windowing systems, or if you don't have a suitable handle, just pass +an empty string for ``"parent_window"``. \ No newline at end of file diff --git a/doc/writing-a-new-backend.rst b/doc/writing-a-new-backend.rst new file mode 100644 index 0000000..437093c --- /dev/null +++ b/doc/writing-a-new-backend.rst @@ -0,0 +1,42 @@ +Writing a New Backend +===================== + +Portal backends are session services that provide user interfaces, and access +APIs and resources specific to the desktop environment they are running. For +example, the KDE backend may use KDE-specific technologies, toolkit, and APIs. + +For a new portal backend (let's call it ``foo``) to be discoverable by XDG +Desktop Portal, the following steps are necessary: + +* Implement one or more :doc:`backend D-Bus interfaces ` + in an executable. This executable must be `D-Bus activatable + `_. +* Install ``foo.portal`` file under ``{DATADIR}/xdg-desktop-portal/portals``. + Usually, ``{DATADIR}`` is ``/usr/share``. The syntax of the portal file is + documented below. +* Make sure the new backend is picked by XDG Desktop Portal by adding a config + file that points to the new ``foo`` portal backend. The syntax of the config + file is documented below. + +Portal (`.portal`) File +----------------------- + +Portal files are files that allow XDG Desktop Portal to know which portal +backends are available, and which backend D-Bus interfaces they implement. +They usually look like this: + +.. code-block:: + + [portal] + DBusName=org.freedesktop.impl.portal.desktop.foo + Interfaces=org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Clipboard;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Lockdown;org.freedesktop.impl.portal.RemoteDesktop;org.freedesktop.impl.portal.ScreenCast; + UseIn=gnome + +The following keys are supported in this file: + +* ``DBusName``: the D-Bus activation name of the portal backend service. +* ``Interfaces``: which :doc:`backend D-Bus interfaces ` + this portal backend implements. +* ``UseIn``: which desktop environments the portal backend should run. **This + key is officially deprecated, and has been replaced by the config file**, but + it's recommended to keep it there for legacy systems. diff --git a/doc/xmlto-config.xsl b/doc/xmlto-config.xsl deleted file mode 100644 index b07ef51..0000000 --- a/doc/xmlto-config.xsl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - diff --git a/document-portal/document-portal-fuse.c b/document-portal/document-portal-fuse.c index 8d2e107..0daeadd 100644 --- a/document-portal/document-portal-fuse.c +++ b/document-portal/document-portal-fuse.c @@ -2,10 +2,12 @@ * Copyright © 2018 Red Hat, Inc * Copyright © 2023 GNOME Foundation Inc. * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -64,6 +66,18 @@ #define ENODATA ENOATTR #endif +#define XDP_XATTR_HOST_PATH "user.document-portal.host-path" +/* man listxattr: + * The list is the set of (null-terminated) names, one after the other. + * + * Example: + * user.name1\0system.name1\0user.name2\0 + * + * We can statically define the string by doing this: + * XDP_XATTR_MY_ATTR "\0" XDP_XATTR_ANOTHER_ATTR + */ +#define XDP_XATTR_ATTRIBUTE_NAME_LIST XDP_XATTR_HOST_PATH + /* Inode ownership model * * The document portal exposes something as a filesystem that it @@ -109,7 +123,7 @@ * referenced by the dcache (Directory Entry Cache) (even though we * will not really use the dcache info due to the 0 valid time). This * is unfortunate, because it means we will keep a lot of file - * descriptor open. But, we can not know if the kernel needs the inode + * descriptors open. But, we can not know if the kernel needs the inode * for some non-dcache use so we can't close the file descriptors. * * To work around this we regularly emit entry invalidation calls @@ -130,6 +144,9 @@ static pthread_t fuse_pthread = 0; static uid_t my_uid; static gid_t my_gid; +static GList *open_files = NULL; +G_LOCK_DEFINE (open_files); + /* from libfuse */ #define FUSE_UNKNOWN_INO 0xffffffff @@ -228,6 +245,7 @@ struct _XdpInode { typedef struct { int fd; + GList *link; } XdpFile; @@ -248,6 +266,10 @@ typedef struct { char name[0]; } XdpInvalidateData; +typedef struct { + gboolean use_splice; +} XdpFuseOptions; + static GList *invalidate_list; G_LOCK_DEFINE (invalidate_list); @@ -589,7 +611,7 @@ xdp_domain_new_by_app (XdpInode *root_inode) } static XdpDomain * -xdp_domain_new_app (XdpInode *parent_inode, +xdp_domain_new_app (XdpInode *parent_inode, const char *app_id) { XdpDomain *parent = parent_inode->domain; @@ -608,8 +630,8 @@ xdp_document_domain_is_dir (XdpDomain *domain) } static XdpDomain * -xdp_domain_new_document (XdpDomain *parent, - const char *doc_id, +xdp_domain_new_document (XdpDomain *parent, + const char *doc_id, PermissionDbEntry *doc_entry) { XdpDomain *domain = _xdp_domain_new (XDP_DOMAIN_DOCUMENT); @@ -772,7 +794,7 @@ generate_persistent_ino (DevIno *backing_devino, /* takes ownership of fd */ static XdpInode * -xdp_inode_new (XdpDomain *domain, +xdp_inode_new (XdpDomain *domain, XdpPhysicalInode *physical) { XdpInode *inode = _xdp_inode_new (); @@ -947,7 +969,7 @@ verify_doc_dir_devino (int dirfd, XdpDomain *doc_domain) return -ENOENT; return 0; - } +} /* Only for toplevel dirs, not this is a bit weird for toplevel dir inodes as it returns the dir itself which isn't really the dirfd @@ -956,7 +978,7 @@ static int xdp_nonphysical_document_inode_opendir (XdpInode *inode) { XdpDomain *domain = inode->domain; - xdp_autofd int dirfd = -1; + g_autofd int dirfd = -1; int res; g_assert (domain->type == XDP_DOMAIN_DOCUMENT); @@ -970,12 +992,12 @@ xdp_nonphysical_document_inode_opendir (XdpInode *inode) if (res != 0) return res; - return xdp_steal_fd (&dirfd); + return g_steal_fd (&dirfd); } static int xdp_document_inode_ensure_dirfd (XdpInode *inode, - int *close_fd_out) + int *close_fd_out) { int close_fd; @@ -1029,21 +1051,21 @@ gen_temp_name (gchar *tmpl) } static int -open_temp_at (int dirfd, - const char *orig_name, - char **name_out, - mode_t mode) +open_temp_at (int dirfd, + const char *orig_name, + char **name_out, + mode_t mode) { - int fd; - int errsv; - const guint count_max = 100; g_autofree char *tmp = g_strconcat (".xdp-", orig_name, "-XXXXXX", NULL); + const size_t count_max = 100; + int errsv; + int fd; - for (int count = 0; count < count_max; count++) + for (size_t count = 0; count < count_max; count++) { gen_temp_name (tmp); - fd = openat (dirfd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|O_RDWR, mode); + fd = openat (dirfd, tmp, O_CREAT | O_EXCL | O_NOFOLLOW | O_NOCTTY | O_RDWR, mode); errsv = errno; if (fd < 0) { @@ -1063,16 +1085,16 @@ open_temp_at (int dirfd, /* allocates tempfile for existing file, Called with tempfile lock held, sets errno */ static int -get_tempfile_for (XdpInode *parent, - XdpDomain *domain, - const char *name, - int dirfd, - const char *tmpname, +get_tempfile_for (XdpInode *parent, + XdpDomain *domain, + const char *name, + int dirfd, + const char *tmpname, XdpTempfile **tempfile_out) { g_autoptr(XdpTempfile) tempfile = NULL; g_autoptr(XdpInode) inode = NULL; - xdp_autofd int o_path_fd = -1; + g_autofd int o_path_fd = -1; int res; if (tempfile_out != NULL) @@ -1082,7 +1104,7 @@ get_tempfile_for (XdpInode *parent, if (o_path_fd == -1) return -errno; - res = ensure_docdir_inode (parent, xdp_steal_fd (&o_path_fd), NULL, &inode); /* passed ownership of o_path_fd */ + res = ensure_docdir_inode (parent, g_steal_fd (&o_path_fd), NULL, &inode); /* passed ownership of o_path_fd */ if (res != 0) return res; @@ -1101,17 +1123,17 @@ get_tempfile_for (XdpInode *parent, /* Creates a new file on disk, Called with tempfile lock held, sets errno */ static int -create_tempfile (XdpInode *parent, - XdpDomain *domain, - const char *name, - int dirfd, - mode_t mode, - XdpTempfile **tempfile_out) +create_tempfile (XdpInode *parent, + XdpDomain *domain, + const char *name, + int dirfd, + mode_t mode, + XdpTempfile **tempfile_out) { g_autoptr(XdpInode) inode = NULL; g_autofree char *real_fd_path = NULL; - xdp_autofd int real_fd = -1; - xdp_autofd int o_path_fd = -1; + g_autofd int real_fd = -1; + g_autofd int o_path_fd = -1; g_autoptr(XdpTempfile) tempfile = NULL; g_autofree char *tmpname = NULL; int res; @@ -1129,9 +1151,9 @@ create_tempfile (XdpInode *parent, return -errno; /* We can close the tmpfd early */ - close (xdp_steal_fd (&real_fd)); + close (g_steal_fd (&real_fd)); - res = ensure_docdir_inode (parent, xdp_steal_fd (&o_path_fd), NULL, &inode); /* passed ownership of o_path_fd */ + res = ensure_docdir_inode (parent, g_steal_fd (&o_path_fd), NULL, &inode); /* passed ownership of o_path_fd */ if (res != 0) return res; @@ -1148,13 +1170,16 @@ create_tempfile (XdpInode *parent, } static int -xdp_document_inode_open_child_fd (XdpInode *inode, const char *name, int open_flags, mode_t mode) +xdp_document_inode_open_child_fd (XdpInode *inode, + const char *name, + int open_flags, + mode_t mode) { XdpDomain *domain = inode->domain; XdpTempfile *tempfile_lookup = NULL; g_autoptr(XdpTempfile) tempfile = NULL; int tempfile_res = 0; - xdp_autofd int fd = -1; + g_autofd int fd = -1; g_assert (domain->type == XDP_DOMAIN_DOCUMENT); @@ -1169,11 +1194,11 @@ xdp_document_inode_open_child_fd (XdpInode *inode, const char *name, int open_fl if (fd == -1) return -errno; - return xdp_steal_fd (&fd); + return g_steal_fd (&fd); } else { - xdp_autofd int dirfd = -1; + g_autofd int dirfd = -1; if (xdp_document_domain_is_dir (domain)) { @@ -1187,7 +1212,7 @@ xdp_document_inode_open_child_fd (XdpInode *inode, const char *name, int open_fl fd = openat (dirfd, ".", open_flags, mode); if (fd == -1) return -errno; - return xdp_steal_fd (&fd); + return g_steal_fd (&fd); } } else @@ -1202,7 +1227,7 @@ xdp_document_inode_open_child_fd (XdpInode *inode, const char *name, int open_fl fd = openat (dirfd, name, open_flags, mode); if (fd == -1) return -errno; - return xdp_steal_fd (&fd); + return g_steal_fd (&fd); } /* Not main file, maybe a temporary file? */ @@ -1231,7 +1256,7 @@ xdp_document_inode_open_child_fd (XdpInode *inode, const char *name, int open_fl if (fd == -1) return -errno; - return xdp_steal_fd (&fd); + return g_steal_fd (&fd); } else { @@ -1261,7 +1286,7 @@ xdp_document_inode_get_self_as_path (XdpInode *inode) } static void -tweak_statbuf_for_document_inode (XdpInode *inode, +tweak_statbuf_for_document_inode (XdpInode *inode, struct stat *buf) { XdpDomain *domain = inode->domain; @@ -1278,7 +1303,9 @@ tweak_statbuf_for_document_inode (XdpInode *inode, } static void -xdp_reply_err (const char *op, fuse_req_t req, int err) +xdp_reply_err (const char *op, + fuse_req_t req, + int err) { if (err != 0) { @@ -1329,10 +1356,10 @@ typedef enum { } XdpDocumentChecks; static gboolean -xdp_document_inode_checks (const char *op, - fuse_req_t req, - XdpInode *inode, - XdpDocumentChecks checks) +xdp_document_inode_checks (const char *op, + fuse_req_t req, + XdpInode *inode, + XdpDocumentChecks checks) { XdpDomain *domain = inode->domain; gboolean check_is_directory = (checks & CHECK_IS_DIRECTORY) != 0; @@ -1378,7 +1405,7 @@ xdp_document_inode_checks (const char *op, } static void -stat_virtual_inode (XdpInode *inode, +stat_virtual_inode (XdpInode *inode, struct stat *buf) { memset (buf, 0, sizeof (struct stat)); @@ -1417,18 +1444,18 @@ stat_virtual_inode (XdpInode *inode, } static void -xdp_fuse_getattr (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_getattr (fuse_req_t req, + fuse_ino_t ino, struct fuse_file_info *fi) { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); XdpDomain *domain = inode->domain; + double attr_valid_time = 0.0;/* Time in secs for attribute validation */ struct stat buf; int res; - double attr_valid_time = 0.0;/* Time in secs for attribute validation */ const char *op = "GETATTR"; - g_debug ("GETATTR %lx", ino); + g_debug ("GETATTR %" G_GINT64_MODIFIER "x", ino); if (xdp_domain_is_virtual_type (domain)) { @@ -1440,12 +1467,15 @@ xdp_fuse_getattr (fuse_req_t req, g_assert (domain->type == XDP_DOMAIN_DOCUMENT); if (inode->physical) - res = fstatat (inode->physical->fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + { + res = fstatat (inode->physical->fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + } else { stat_virtual_inode (inode, &buf); res = 0; } + if (res == -1) return xdp_reply_err (op, req, errno); @@ -1468,7 +1498,7 @@ xdp_fuse_setattr (fuse_req_t req, int res; const char *op = "SETATTR"; - g_debug ("SETATTR %lx %s", ino, to_set_string); + g_debug ("SETATTR %" G_GINT64_MODIFIER "x %s", ino, to_set_string); if (!xdp_document_inode_checks (op, req, inode, CHECK_CAN_WRITE | CHECK_IS_PHYSICAL)) @@ -1591,8 +1621,8 @@ xdp_fuse_setattr (fuse_req_t req, } static void -prepare_reply_entry (XdpInode *inode, - struct stat *buf, +prepare_reply_entry (XdpInode *inode, + struct stat *buf, struct fuse_entry_param *e) { xdp_inode_kernel_ref (inode); /* Ref given to the kernel, returned in xdp_forget() */ @@ -1604,7 +1634,7 @@ prepare_reply_entry (XdpInode *inode, } static void -prepare_reply_virtual_entry (XdpInode *inode, +prepare_reply_virtual_entry (XdpInode *inode, struct fuse_entry_param *e) { stat_virtual_inode (inode, &e->attr); @@ -1626,18 +1656,21 @@ abort_reply_entry (struct fuse_entry_param *e) } static int -ensure_docdir_inode (XdpInode *parent, - int o_path_fd_in, /* Takes ownership */ - struct fuse_entry_param *e, - XdpInode **inode_out) +ensure_docdir_inode (XdpInode *parent, + int o_path_fd_in, + struct fuse_entry_param *e, + XdpInode **inode_out) { XdpDomain *domain = parent->domain; g_autoptr(XdpPhysicalInode) physical = NULL; g_autoptr(XdpInode) inode = NULL; - xdp_autofd int o_path_fd = o_path_fd_in; + g_autofd int o_path_fd = -1; struct stat buf; int res; + /* Take ownership */ + o_path_fd = g_steal_fd (&o_path_fd_in); + res = fstatat (o_path_fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); if (res == -1) return -errno; @@ -1646,9 +1679,9 @@ ensure_docdir_inode (XdpInode *parent, if (!xdp_document_domain_is_dir (domain) && !S_ISREG(buf.st_mode)) return -ENOENT; - physical = ensure_physical_inode (buf.st_dev, buf.st_ino, xdp_steal_fd (&o_path_fd)); /* passed ownership of fd */ + physical = ensure_physical_inode (buf.st_dev, buf.st_ino, g_steal_fd (&o_path_fd)); /* passed ownership of fd */ - G_LOCK(domain_inodes); + G_LOCK (domain_inodes); inode = g_hash_table_lookup (domain->inodes, physical); if (inode != NULL) inode = xdp_inode_ref (inode); @@ -1659,9 +1692,9 @@ ensure_docdir_inode (XdpInode *parent, inode->domain_root_inode = xdp_inode_ref (parent->domain_root_inode); else inode->domain_root_inode = xdp_inode_ref (parent); - g_hash_table_insert (domain->inodes, physical, inode); + g_hash_table_insert (domain->inodes, physical, inode); } - G_UNLOCK(domain_inodes); + G_UNLOCK (domain_inodes); if (e) { @@ -1676,9 +1709,9 @@ ensure_docdir_inode (XdpInode *parent, } static int -ensure_docdir_inode_by_name (XdpInode *parent, - int dirfd, - const char *name, +ensure_docdir_inode_by_name (XdpInode *parent, + int dirfd, + const char *name, struct fuse_entry_param *e) { int o_path_fd; @@ -1692,7 +1725,7 @@ ensure_docdir_inode_by_name (XdpInode *parent, static XdpInode * -ensure_by_app_inode (XdpInode *by_app_inode, +ensure_by_app_inode (XdpInode *by_app_inode, const char *app_id) { XdpDomain *by_app_domain = by_app_inode->domain; @@ -1701,7 +1734,7 @@ ensure_by_app_inode (XdpInode *by_app_inode, if (!xdp_is_valid_app_id (app_id)) return NULL; - G_LOCK(domain_inodes); + G_LOCK (domain_inodes); inode = g_hash_table_lookup (by_app_domain->inodes, app_id); if (inode != NULL) inode = xdp_inode_ref (inode); @@ -1711,17 +1744,17 @@ ensure_by_app_inode (XdpInode *by_app_inode, inode = xdp_inode_new (app_domain, NULL); g_hash_table_insert (by_app_domain->inodes, app_domain->app_id, inode); } - G_UNLOCK(domain_inodes); + G_UNLOCK (domain_inodes); return g_steal_pointer (&inode); } static XdpInode * -ensure_doc_inode (XdpInode *parent, +ensure_doc_inode (XdpInode *parent, const char *doc_id) { - g_autoptr(XdpInode) inode = NULL; g_autoptr(PermissionDbEntry) doc_entry = NULL; + g_autoptr(XdpInode) inode = NULL; XdpDomain *parent_domain = parent->domain; doc_entry = xdp_lookup_doc (doc_id); @@ -1731,7 +1764,7 @@ ensure_doc_inode (XdpInode *parent, !app_can_see_doc (doc_entry, parent_domain->app_id))) return NULL; - G_LOCK(domain_inodes); + G_LOCK (domain_inodes); inode = g_hash_table_lookup (parent_domain->inodes, doc_id); if (inode != NULL) inode = xdp_inode_ref (inode); @@ -1742,7 +1775,7 @@ ensure_doc_inode (XdpInode *parent, inode = xdp_inode_new (doc_domain, NULL); g_hash_table_insert (parent_domain->inodes, doc_domain->doc_id, inode); } - G_UNLOCK(domain_inodes); + G_UNLOCK (domain_inodes); return g_steal_pointer (&inode); } @@ -1776,7 +1809,8 @@ invalidate_dentry_cb (gpointer user_data) * which will free up a bunch of O_PATH fds in the fuse implementation. */ static void -queue_invalidate_dentry (XdpInode *parent, const char *name) +queue_invalidate_dentry (XdpInode *parent, + const char *name) { XDP_AUTOLOCK (invalidate_list); @@ -1798,8 +1832,8 @@ queue_invalidate_dentry (XdpInode *parent, const char *name) } static void -xdp_fuse_lookup (fuse_req_t req, - fuse_ino_t parent_ino, +xdp_fuse_lookup (fuse_req_t req, + fuse_ino_t parent_ino, const char *name) { g_autoptr(XdpInode) parent = xdp_inode_from_ino (parent_ino); @@ -1810,7 +1844,7 @@ xdp_fuse_lookup (fuse_req_t req, int open_flags = O_PATH|O_NOFOLLOW; const char *op = "LOOKUP"; - g_debug ("LOOKUP %lx:%s", parent_ino, name); + g_debug ("LOOKUP %" G_GINT64_MODIFIER "x:%s", parent_ino, name); if (strcmp (name, ".") == 0 || strcmp (name, "..") == 0) { @@ -1860,7 +1894,7 @@ xdp_fuse_lookup (fuse_req_t req, queue_invalidate_dentry (parent, name); } - g_debug ("LOOKUP %lx:%s => %lx", parent_ino, name, e.ino); + g_debug ("LOOKUP %" G_GINT64_MODIFIER "x:%s => %" G_GINT64_MODIFIER "x", parent_ino, name, e.ino); if (fuse_reply_entry (req, &e) == -ENOENT) abort_reply_entry (&e); @@ -1871,19 +1905,29 @@ xdp_file_new (int fd) { XdpFile *file = g_new0 (XdpFile, 1); file->fd = fd; + + XDP_AUTOLOCK (open_files); + open_files = g_list_prepend (open_files, file); + file->link = open_files; + return file; } static void xdp_file_free (XdpFile *file) { + GList *link = g_steal_pointer (&file->link); + + XDP_AUTOLOCK (open_files); + open_files = g_list_delete_link (open_files, link); + close (file->fd); g_free (file); } static void -xdp_fuse_open (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_open (fuse_req_t req, + fuse_ino_t ino, struct fuse_file_info *fi) { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); @@ -1895,7 +1939,7 @@ xdp_fuse_open (fuse_req_t req, XdpDocumentChecks checks; const char *op = "OPEN"; - g_debug ("OPEN %lx %s", ino, open_flags_string); + g_debug ("OPEN %" G_GINT64_MODIFIER "x %s", ino, open_flags_string); checks = CHECK_IS_PHYSICAL; if (open_flags_has_write (open_flags)) @@ -1955,13 +1999,13 @@ xdp_fuse_create (fuse_req_t req, g_autofree char *open_flags_string = open_flags_to_string (open_flags); struct fuse_entry_param e; int res; - xdp_autofd int fd = -1; - xdp_autofd int o_path_fd = -1; + g_autofd int fd = -1; + g_autofd int o_path_fd = -1; g_autofree char *fd_path = NULL; XdpFile *file = NULL; const char *op = "CREATE"; - g_debug ("CREATE %lx %s %s, 0%o", parent_ino, filename, open_flags_string, mode); + g_debug ("CREATE %" G_GINT64_MODIFIER "x %s %s, 0%o", parent_ino, filename, open_flags_string, mode); if (!xdp_document_inode_checks (op, req, parent, CHECK_CAN_WRITE | @@ -1977,11 +2021,11 @@ xdp_fuse_create (fuse_req_t req, if (o_path_fd < 0) return xdp_reply_err (op, req, errno); - res = ensure_docdir_inode (parent, xdp_steal_fd (&o_path_fd), &e, NULL); /* Takes ownership of o_path_fd */ + res = ensure_docdir_inode (parent, g_steal_fd (&o_path_fd), &e, NULL); /* Takes ownership of o_path_fd */ if (res != 0) return xdp_reply_err (op, req, -res); - file = xdp_file_new (xdp_steal_fd (&fd)); /* Takes ownership of fd */ + file = xdp_file_new (g_steal_fd (&fd)); /* Takes ownership of fd */ fi->fh = (gsize)file; if (fuse_reply_create (req, &e, fi) == -ENOENT) @@ -1995,22 +2039,27 @@ xdp_fuse_create (fuse_req_t req, } static void -xdp_fuse_read (fuse_req_t req, - fuse_ino_t ino, - size_t size, - off_t off, +xdp_fuse_read (fuse_req_t req, + fuse_ino_t ino, + size_t size, + off_t off, struct fuse_file_info *fi) { struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size); XdpFile *file = (XdpFile *)fi->fh; + enum fuse_buf_copy_flags reply_flags = FUSE_BUF_SPLICE_MOVE; + XdpFuseOptions *fuse_opts = fuse_req_userdata (req); - g_debug ("READ %lx size %ld off %ld", ino, size, off); + g_debug ("READ %" G_GINT64_MODIFIER "x size %" G_GSIZE_FORMAT " off %" G_GOFFSET_FORMAT, ino, size, (goffset)off); buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; buf.buf[0].fd = file->fd; buf.buf[0].pos = off; - fuse_reply_data (req, &buf, FUSE_BUF_SPLICE_MOVE); + if (!fuse_opts->use_splice) + reply_flags = FUSE_BUF_NO_SPLICE; + + fuse_reply_data (req, &buf, reply_flags); } @@ -2026,7 +2075,7 @@ xdp_fuse_write (fuse_req_t req, ssize_t res; const char *op = "WRITE"; - g_debug ("WRITE %lx size %ld off %ld", ino, size, off); + g_debug ("WRITE %" G_GINT64_MODIFIER "x size %" G_GSIZE_FORMAT " off %" G_GOFFSET_FORMAT, ino, size, (goffset)off); res = pwrite (file->fd, buf, size, off); @@ -2047,14 +2096,19 @@ xdp_fuse_write_buf (fuse_req_t req, struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(bufv)); ssize_t res; const char *op = "WRITEBUF"; + enum fuse_buf_copy_flags copy_flags = FUSE_BUF_SPLICE_NONBLOCK; + XdpFuseOptions *fuse_opts = fuse_req_userdata (req); - g_debug ("WRITEBUF %lx off %ld", ino, off); + g_debug ("WRITEBUF %" G_GINT64_MODIFIER "x off %" G_GOFFSET_FORMAT, ino, (goffset)off); dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; dst.buf[0].fd = file->fd; dst.buf[0].pos = off; - res = fuse_buf_copy (&dst, bufv, FUSE_BUF_SPLICE_NONBLOCK); + if (!fuse_opts->use_splice) + copy_flags = FUSE_BUF_NO_SPLICE; + + res = fuse_buf_copy (&dst, bufv, copy_flags); if (res >= 0) fuse_reply_write (req, res); else @@ -2071,7 +2125,7 @@ xdp_fuse_fsync (fuse_req_t req, int res; const char *op = "FSYNC"; - g_debug ("FSYNC %lx", ino); + g_debug ("FSYNC %" G_GINT64_MODIFIER "x", ino); if (datasync) res = fdatasync (file->fd); @@ -2084,18 +2138,18 @@ xdp_fuse_fsync (fuse_req_t req, } static void -xdp_fuse_fallocate (fuse_req_t req, - fuse_ino_t ino, - int mode, - off_t offset, - off_t length, +xdp_fuse_fallocate (fuse_req_t req, + fuse_ino_t ino, + int mode, + off_t offset, + off_t length, struct fuse_file_info *fi) { XdpFile *file = (XdpFile *)fi->fh; int res; const char *op = "FALLOCATE"; - g_debug ("FALLOCATE %lx", ino); + g_debug ("FALLOCATE %" G_GINT64_MODIFIER "x", ino); #ifdef __linux__ res = fallocate (file->fd, mode, offset, length); @@ -2110,13 +2164,13 @@ xdp_fuse_fallocate (fuse_req_t req, } static void -xdp_fuse_flush (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_flush (fuse_req_t req, + fuse_ino_t ino, struct fuse_file_info *fi) { const char *op = "FLUSH"; - g_debug ("FLUSH %lx", ino); + g_debug ("FLUSH %" G_GINT64_MODIFIER "x", ino); xdp_reply_ok (op, req); } @@ -2128,7 +2182,7 @@ xdp_fuse_release (fuse_req_t req, XdpFile *file = (XdpFile *)fi->fh; const char *op = "RELEASE"; - g_debug ("RELEASE %lx", ino); + g_debug ("RELEASE %" G_GINT64_MODIFIER "x", ino); xdp_file_free (file); @@ -2141,27 +2195,27 @@ forget_one (fuse_ino_t ino, { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); - g_debug ("FORGET %lx %ld", ino, nlookup); + g_debug ("FORGET %" G_GINT64_MODIFIER "x %ld", ino, nlookup); xdp_inode_kernel_unref (inode, nlookup); } static void xdp_fuse_forget (fuse_req_t req, fuse_ino_t ino, - uint64_t nlookup) + uint64_t nlookup) { forget_one (ino, nlookup); fuse_reply_none (req); } static void -xdp_fuse_forget_multi (fuse_req_t req, - size_t count, +xdp_fuse_forget_multi (fuse_req_t req, + size_t count, struct fuse_forget_data *forgets) { size_t i; - g_debug ("FORGET_MULTI %ld", count); + g_debug ("FORGET_MULTI %" G_GSIZE_FORMAT, count); for (i = 0; i < count; i++) forget_one (forgets[i].ino, forgets[i].nlookup); @@ -2179,10 +2233,10 @@ xdp_dir_free (XdpDir *d) } static void -xdp_dir_add (XdpDir *d, - fuse_req_t req, - const char *name, - mode_t mode) +xdp_dir_add (XdpDir *d, + fuse_req_t req, + const char *name, + mode_t mode) { struct stat stbuf; @@ -2274,11 +2328,9 @@ xdp_fuse_opendir (fuse_req_t req, g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); XdpDomain *domain = inode->domain; XdpDir *d = NULL; - int open_flags = O_RDONLY | O_DIRECTORY; - DIR *dir; const char *op = "OPENDIR"; - g_debug ("OPENDIR %lx domain %d", ino, inode->domain->type); + g_debug ("OPENDIR %" G_GINT64_MODIFIER "x domain %d", ino, inode->domain->type); if (xdp_domain_is_virtual_type (domain)) { @@ -2307,7 +2359,10 @@ xdp_fuse_opendir (fuse_req_t req, { if (inode->physical) { - int fd = openat (inode->physical->fd, ".", open_flags, 0); + DIR *dir; + int fd; + + fd = openat (inode->physical->fd, ".", O_RDONLY | O_DIRECTORY, 0); if (fd < 0) return xdp_reply_err (op, req, errno); @@ -2360,7 +2415,7 @@ xdp_fuse_opendir (fuse_req_t req, } } - fi->fh = (gsize)d; + fi->fh = (gsize)d; if (fuse_reply_open (req, fi) == -ENOENT) { @@ -2370,22 +2425,22 @@ xdp_fuse_opendir (fuse_req_t req, } static void -xdp_fuse_readdir (fuse_req_t req, - fuse_ino_t ino, - size_t size, - off_t off, +xdp_fuse_readdir (fuse_req_t req, + fuse_ino_t ino, + size_t size, + off_t off, struct fuse_file_info *fi) { XdpDir *d = (XdpDir *)fi->fh; - size_t rem; const char *op = "READDIR"; - g_debug ("READDIR %lx %ld %ld", ino, size, off); + g_debug ("READDIR %" G_GINT64_MODIFIER "x %" G_GSIZE_FORMAT " %" G_GOFFSET_FORMAT, ino, size, (goffset)off); if (d->dir) { - char *p; g_autofree char *buf = g_try_malloc (size); + size_t rem; + char *p; if (buf == NULL) { @@ -2451,11 +2506,13 @@ xdp_fuse_readdir (fuse_req_t req, if (off < d->dirbuf_size) { gsize reply_size = MIN (d->dirbuf_size - off, size); - g_autofree char *buf = g_memdup (d->dirbuf + off, reply_size); + g_autofree char *buf = g_memdup2 (d->dirbuf + off, reply_size); fuse_reply_buf (req, buf, reply_size); } else - fuse_reply_buf (req, NULL, 0); + { + fuse_reply_buf (req, NULL, 0); + } } } @@ -2467,7 +2524,7 @@ xdp_fuse_releasedir (fuse_req_t req, XdpDir *d = (XdpDir *)fi->fh; const char *op = "RELEASEDIR"; - g_debug ("RELEASEDIR %lx", ino); + g_debug ("RELEASEDIR %" G_GINT64_MODIFIER "x", ino); xdp_dir_free (d); @@ -2484,7 +2541,7 @@ xdp_fuse_fsyncdir (fuse_req_t req, int fd, res; const char *op = "FSYNCDIR"; - g_debug ("FSYNCDIR %lx", ino); + g_debug ("FSYNCDIR %" G_GINT64_MODIFIER "x", ino); if (dir->dir) { @@ -2505,18 +2562,18 @@ xdp_fuse_fsyncdir (fuse_req_t req, static void xdp_fuse_mkdir (fuse_req_t req, - fuse_ino_t parent_ino, + fuse_ino_t parent_ino, const char *name, - mode_t mode) + mode_t mode) { g_autoptr(XdpInode) parent = xdp_inode_from_ino (parent_ino); struct fuse_entry_param e; int res; - xdp_autofd int close_fd = -1; + g_autofd int close_fd = -1; int dirfd; const char *op = "MKDIR"; - g_debug ("MKDIR %lx %s", parent_ino, name); + g_debug ("MKDIR %" G_GINT64_MODIFIER "x %s", parent_ino, name); if (!xdp_document_inode_checks (op, req, parent, CHECK_CAN_WRITE | @@ -2550,7 +2607,7 @@ xdp_fuse_unlink (fuse_req_t req, int res = -1; const char * op = "UNLINK"; - g_debug ("UNLINK %lx %s", parent_ino, filename); + g_debug ("UNLINK %" G_GINT64_MODIFIER "x %s", parent_ino, filename); if (!xdp_document_inode_checks (op, req, parent, CHECK_CAN_WRITE | @@ -2565,7 +2622,7 @@ xdp_fuse_unlink (fuse_req_t req, } else { - xdp_autofd int dirfd = -1; + g_autofd int dirfd = -1; /* Only reached for non-directory inodes */ @@ -2597,11 +2654,11 @@ xdp_fuse_unlink (fuse_req_t req, } static int -try_renameat (int olddirfd, - const char *oldpath, - int newdirfd, - const char *newpath, - unsigned int flags) +try_renameat (int olddirfd, + const char *oldpath, + int newdirfd, + const char *newpath, + unsigned int flags) { #if HAVE_RENAMEAT2 return renameat2 (olddirfd, oldpath, newdirfd, newpath, flags); @@ -2619,12 +2676,12 @@ try_renameat (int olddirfd, static void -xdp_fuse_rename (fuse_req_t req, - fuse_ino_t parent_ino, - const char *name, - fuse_ino_t newparent_ino, - const char *newname, - unsigned int flags) +xdp_fuse_rename (fuse_req_t req, + fuse_ino_t parent_ino, + const char *name, + fuse_ino_t newparent_ino, + const char *newname, + unsigned int flags) { g_autoptr(XdpInode) parent = xdp_inode_from_ino (parent_ino); g_autoptr(XdpInode) newparent = xdp_inode_from_ino (newparent_ino); @@ -2632,11 +2689,11 @@ xdp_fuse_rename (fuse_req_t req, XdpDomain *domain; int res, errsv; int olddirfd, newdirfd, dirfd; - xdp_autofd int close_fd1 = -1; - xdp_autofd int close_fd2 = -1; + g_autofd int close_fd1 = -1; + g_autofd int close_fd2 = -1; const char *op = "RENAME"; - g_debug ("RENAME %lx %s -> %lx %s (flags: %s)", parent_ino, name, + g_debug ("RENAME %" G_GINT64_MODIFIER "x %s -> %" G_GINT64_MODIFIER "x %s (flags: %s)", parent_ino, name, newparent_ino, newname, rename_flags_string); if (!xdp_document_inode_checks (op, req, parent, @@ -2783,14 +2840,14 @@ xdp_fuse_rename (fuse_req_t req, static void xdp_fuse_access (fuse_req_t req, fuse_ino_t ino, - int mask) + int mask) { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); g_autofree char *path = NULL; int res; const char *op = "ACCESS"; - g_debug ("ACCESS %lx", ino); + g_debug ("ACCESS %" G_GINT64_MODIFIER "x", ino); if (inode->domain->type != XDP_DOMAIN_DOCUMENT) { @@ -2829,17 +2886,17 @@ xdp_fuse_access (fuse_req_t req, } static void -xdp_fuse_rmdir (fuse_req_t req, - fuse_ino_t parent_ino, +xdp_fuse_rmdir (fuse_req_t req, + fuse_ino_t parent_ino, const char *filename) { g_autoptr(XdpInode) parent = xdp_inode_from_ino (parent_ino); - xdp_autofd int close_fd = -1; + g_autofd int close_fd = -1; int dirfd; int res; const char *op = "RMDIR"; - g_debug ("RMDIR %lx %s", parent_ino, filename); + g_debug ("RMDIR %" G_GINT64_MODIFIER "x %s", parent_ino, filename); if (!xdp_document_inode_checks (op, req, parent, CHECK_CAN_WRITE | @@ -2864,10 +2921,10 @@ xdp_fuse_readlink (fuse_req_t req, { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); char linkname[PATH_MAX + 1]; - ssize_t res; const char *op = "READLINK"; + ssize_t res; - g_debug ("READLINK %lx", ino); + g_debug ("READLINK %" G_GINT64_MODIFIER "x", ino); if (!xdp_document_inode_checks (op, req, inode, CHECK_IS_DIRECTORY | @@ -2886,19 +2943,19 @@ xdp_fuse_readlink (fuse_req_t req, } static void -xdp_fuse_symlink (fuse_req_t req, +xdp_fuse_symlink (fuse_req_t req, const char *link, - fuse_ino_t parent_ino, + fuse_ino_t parent_ino, const char *name) { g_autoptr(XdpInode) parent = xdp_inode_from_ino (parent_ino); - int res; - int dirfd; - xdp_autofd int close_fd = -1; + g_autofd int close_fd = -1; struct fuse_entry_param e; const char * op = "SYMLINK"; + int dirfd; + int res; - g_debug ("SYMLINK %s %lx %s", link, parent_ino, name); + g_debug ("SYMLINK %s %" G_GINT64_MODIFIER "x %s", link, parent_ino, name); if (!xdp_document_inode_checks (op, req, parent, CHECK_CAN_WRITE | @@ -2923,21 +2980,21 @@ xdp_fuse_symlink (fuse_req_t req, } static void -xdp_fuse_link (fuse_req_t req, - fuse_ino_t ino, - fuse_ino_t newparent_ino, +xdp_fuse_link (fuse_req_t req, + fuse_ino_t ino, + fuse_ino_t newparent_ino, const char *newname) { - g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); g_autoptr(XdpInode) newparent = xdp_inode_from_ino (newparent_ino); - int res; + g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); g_autofree char *proc_path = NULL; - int newparent_dirfd; - xdp_autofd int close_fd = -1; + g_autofd int close_fd = -1; struct fuse_entry_param e; const char * op = "LINK"; + int newparent_dirfd; + int res; - g_debug ("LINK %lx %lx %s", ino, newparent_ino, newname); + g_debug ("LINK %" G_GINT64_MODIFIER "x %" G_GINT64_MODIFIER "x %s", ino, newparent_ino, newname); /* hardlinks only supported in docdirs, and only physical files */ if (!xdp_document_inode_checks (op, req, inode, @@ -2977,7 +3034,7 @@ xdp_fuse_statfs (fuse_req_t req, int res; const char *op = "STATFS"; - g_debug ("STATFS %lx", ino); + g_debug ("STATFS %" G_GINT64_MODIFIER "x", ino); if (!xdp_document_inode_checks (op, req, inode, 0)) return; @@ -2993,20 +3050,111 @@ xdp_fuse_statfs (fuse_req_t req, xdp_reply_err (op, req, errno); } +static gboolean +xdp_fuse_get_real_path (XdpPhysicalInode *physical, + char **real_path_out) +{ + g_autofree char *fd_path = fd_to_path (physical->fd); + char path_buffer[PATH_MAX + 1]; + DevIno file_devino = physical->backing_devino; + ssize_t symlink_size; + struct stat buf; + + /* Try to extract a real path to the file + * (and verify it goes to the same place as the fd) */ + symlink_size = readlink (fd_path, path_buffer, PATH_MAX); + if (symlink_size < 1) + return FALSE; + + path_buffer[symlink_size] = 0; + + if (lstat (path_buffer, &buf) != 0 || + buf.st_dev != file_devino.dev || + buf.st_ino != file_devino.ino) + return FALSE; + + *real_path_out = g_strdup (path_buffer); + return TRUE; +} + +static ssize_t +xdp_fuse_set_host_path_xattr (XdpInode *inode, + const char *value, + size_t size) +{ + errno = EPERM; + return -1; +} + +static ssize_t +xdp_fuse_get_host_path_xattr (XdpInode *inode, + char *buf, + size_t size) +{ + const char *path = NULL; + size_t path_size; + g_autofree char *real_path = NULL; + + path = inode->domain->doc_path; + if (!path) + { + errno = ENODATA; + return -1; + } + + if (inode->physical) + { + if (!xdp_fuse_get_real_path (inode->physical, &real_path)) + { + errno = ENODATA; + return -1; + } + + if (!g_str_has_prefix (real_path, path)) + { + errno = ENODATA; + return -1; + } + + path = real_path; + } + + path_size = strlen (path); + + if (size == 0) + return path_size; + + if (size < path_size) + { + errno = ERANGE; + return -1; + } + + memcpy (buf, path, path_size); + return path_size; +} + +static ssize_t +xdp_fuse_remove_host_path_xattr (XdpInode *inode) +{ + errno = EPERM; + return -1; +} + static void -xdp_fuse_setxattr (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_setxattr (fuse_req_t req, + fuse_ino_t ino, const char *name, const char *value, - size_t size, - int flags) + size_t size, + int flags) { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); ssize_t res; g_autofree char *path = NULL; const char *op = "SETXATTR"; - g_debug ("SETXATTR %lx %s", ino, name); + g_debug ("SETXATTR %" G_GINT64_MODIFIER "x %s", ino, name); if (!xdp_document_inode_checks (op, req, inode, CHECK_CAN_WRITE | @@ -3014,14 +3162,21 @@ xdp_fuse_setxattr (fuse_req_t req, CHECK_IS_PHYSICAL)) return; - path = fd_to_path (inode->physical->fd); + if (g_strcmp0 (name, XDP_XATTR_HOST_PATH) == 0) + { + res = xdp_fuse_set_host_path_xattr (inode, value, size); + } + else + { + path = fd_to_path (inode->physical->fd); #if defined(HAVE_SYS_XATTR_H) - res = setxattr (path, name, value, size, flags); + res = setxattr (path, name, value, size, flags); #elif defined(HAVE_SYS_EXTATTR_H) - res = extattr_set_file (path, EXTATTR_NAMESPACE_USER, name, value, size); + res = extattr_set_file (path, EXTATTR_NAMESPACE_USER, name, value, size); #else #error "Not implemented for your platform" #endif + } if (res < 0) return xdp_reply_err (op, req, errno); @@ -3030,10 +3185,10 @@ xdp_fuse_setxattr (fuse_req_t req, } static void -xdp_fuse_getxattr (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_getxattr (fuse_req_t req, + fuse_ino_t ino, const char *name, - size_t size) + size_t size) { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); ssize_t res; @@ -3041,7 +3196,7 @@ xdp_fuse_getxattr (fuse_req_t req, g_autofree char *path = NULL; const char *op = "GETXATTR"; - g_debug ("GETXATTR %lx %s %ld", ino, name, size); + g_debug ("GETXATTR %" G_GINT64_MODIFIER "x %s %" G_GSIZE_FORMAT, ino, name, size); if (inode->domain->type != XDP_DOMAIN_DOCUMENT) return xdp_reply_err (op, req, ENODATA); @@ -3051,17 +3206,23 @@ xdp_fuse_getxattr (fuse_req_t req, path = xdp_document_inode_get_self_as_path (inode); if (path == NULL) - res = ENODATA; + return xdp_reply_err (op, req, ENODATA); + + if (g_strcmp0 (name, XDP_XATTR_HOST_PATH) == 0) + { + res = xdp_fuse_get_host_path_xattr (inode, buf, size); + } else - { + { #if defined(HAVE_SYS_XATTR_H) - res = getxattr (path, name, buf, size); + res = getxattr (path, name, buf, size); #elif defined(HAVE_SYS_EXTATTR_H) - res = extattr_get_file (path, EXTATTR_NAMESPACE_USER, name, buf, size); + res = extattr_get_file (path, EXTATTR_NAMESPACE_USER, name, buf, size); #else #error "Not implemented for your platform" #endif - } + } + if (res < 0) return xdp_reply_err (op, req, errno); @@ -3071,18 +3232,39 @@ xdp_fuse_getxattr (fuse_req_t req, fuse_reply_buf (req, buf, res); } +static ssize_t +xdp_fuse_listxattr_xdp_attrs (XdpInode *inode, + ssize_t res, + char *buf, + size_t size) +{ + size_t attr_size = sizeof (XDP_XATTR_ATTRIBUTE_NAME_LIST); + + if (size == 0) + return res + attr_size; + + if (size < res + attr_size) + { + errno = ERANGE; + return -1; + } + + memcpy (buf + res, XDP_XATTR_ATTRIBUTE_NAME_LIST, attr_size); + return res + attr_size; +} + static void xdp_fuse_listxattr (fuse_req_t req, fuse_ino_t ino, - size_t size) + size_t size) { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); - ssize_t res; - g_autofree char *buf = NULL; g_autofree char *path = NULL; + g_autofree char *buf = NULL; const char *op = "LISTXATTR"; + ssize_t res; - g_debug ("LISTXATTR %lx %ld", ino, size); + g_debug ("LISTXATTR %" G_GINT64_MODIFIER "x %" G_GSIZE_FORMAT, ino, size); if (inode->domain->type != XDP_DOMAIN_DOCUMENT) return xdp_reply_err (op, req, ENOTSUP); @@ -3091,18 +3273,24 @@ xdp_fuse_listxattr (fuse_req_t req, buf = g_malloc (size); path = xdp_document_inode_get_self_as_path (inode); - if (path) - { + + if (path == NULL) + { + res = 0; + } + else + { #if defined(HAVE_SYS_XATTR_H) - res = listxattr (path, buf, size); + res = listxattr (path, buf, size); #elif defined(HAVE_SYS_EXTATTR_H) - res = extattr_list_file (path, EXTATTR_NAMESPACE_USER, buf, size); + res = extattr_list_file (path, EXTATTR_NAMESPACE_USER, buf, size); #else #error "Not implemented for your platform" #endif - } - else - res = 0; + + if (res >= 0) + res = xdp_fuse_listxattr_xdp_attrs (inode, res, buf, size); + } if (res < 0) return xdp_reply_err (op, req, errno); @@ -3114,8 +3302,8 @@ xdp_fuse_listxattr (fuse_req_t req, } static void -xdp_fuse_removexattr (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_removexattr (fuse_req_t req, + fuse_ino_t ino, const char *name) { g_autoptr(XdpInode) inode = xdp_inode_from_ino (ino); @@ -3123,7 +3311,7 @@ xdp_fuse_removexattr (fuse_req_t req, ssize_t res; const char *op = "REMOVEXATTR"; - g_debug ("REMOVEXATTR %lx %s", ino, name); + g_debug ("REMOVEXATTR %" G_GINT64_MODIFIER "x %s", ino, name); if (!xdp_document_inode_checks (op, req, inode, CHECK_CAN_WRITE | @@ -3131,14 +3319,21 @@ xdp_fuse_removexattr (fuse_req_t req, CHECK_IS_PHYSICAL)) return; - path = fd_to_path (inode->physical->fd); + if (g_strcmp0 (name, XDP_XATTR_HOST_PATH) == 0) + { + res = xdp_fuse_remove_host_path_xattr (inode); + } + else + { + path = fd_to_path (inode->physical->fd); #if defined(HAVE_SYS_XATTR_H) - res = removexattr (path, name); + res = removexattr (path, name); #elif defined(HAVE_SYS_EXTATTR_H) - res = extattr_delete_file (path, EXTATTR_NAMESPACE_USER, name); + res = extattr_delete_file (path, EXTATTR_NAMESPACE_USER, name); #else #error "Not implemented for your platform" #endif + } if (res < 0) xdp_reply_err (op, req, errno); @@ -3147,16 +3342,16 @@ xdp_fuse_removexattr (fuse_req_t req, } static void -xdp_fuse_getlk (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_getlk (fuse_req_t req, + fuse_ino_t ino, struct fuse_file_info *fi, - struct flock *lock) + struct flock *lock) { const char *op = "GETLK"; XdpFile *file = (XdpFile *)fi->fh; int res; - g_debug ("GETLK %lx", ino); + g_debug ("GETLK %" G_GINT64_MODIFIER "x", ino); res = fcntl (file->fd, F_GETLK, lock); if (res < 0) @@ -3166,17 +3361,17 @@ xdp_fuse_getlk (fuse_req_t req, } static void -xdp_fuse_setlk (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_setlk (fuse_req_t req, + fuse_ino_t ino, struct fuse_file_info *fi, - struct flock *lock, - int sleep) + struct flock *lock, + int sleep) { const char *op = "SETLK"; XdpFile *file = (XdpFile *)fi->fh; int res; - g_debug ("SETLK %lx", ino); + g_debug ("SETLK %" G_GINT64_MODIFIER "x", ino); res = fcntl (file->fd, F_SETLK, lock); if (res < 0) @@ -3186,14 +3381,14 @@ xdp_fuse_setlk (fuse_req_t req, } static void -xdp_fuse_flock (fuse_req_t req, - fuse_ino_t ino, +xdp_fuse_flock (fuse_req_t req, + fuse_ino_t ino, struct fuse_file_info *fi, - int lock_op) + int lock_op) { const char *op = "FLOCK"; - g_debug ("FLOCK %lx", ino); + g_debug ("FLOCK %" G_GINT64_MODIFIER "x", ino); xdp_reply_err (op, req, ENOSYS); } @@ -3202,16 +3397,22 @@ static void xdp_fuse_init_cb (void *userdata, struct fuse_conn_info *conn) { + XdpFuseOptions *fuse_opts = userdata; + g_debug ("INIT"); - /* splice_read: use splice() to read from fuse pipe */ - conn->want |= FUSE_CAP_SPLICE_READ; - /* splice_write: use splice() to write to fuse pipe */ - conn->want |= FUSE_CAP_SPLICE_WRITE; - /* splice_move: move buffers from writing app to kernel during splice write */ - conn->want |= FUSE_CAP_SPLICE_MOVE; /* atomic_o_trunc: We handle O_TRUNC in create() */ conn->want |= FUSE_CAP_ATOMIC_O_TRUNC; + + if (fuse_opts->use_splice) + { + /* splice_read: use splice() to read from fuse pipe */ + conn->want |= FUSE_CAP_SPLICE_READ; + /* splice_write: use splice() to write to fuse pipe */ + conn->want |= FUSE_CAP_SPLICE_WRITE; + /* splice_move: move buffers from writing app to kernel during splice write */ + conn->want |= FUSE_CAP_SPLICE_MOVE; + } } extern gboolean on_fuse_unmount (void *); @@ -3219,10 +3420,14 @@ extern gboolean on_fuse_unmount (void *); static void xdp_fuse_destroy_cb (void *userdata) { + XdpFuseOptions *fuse_opts = userdata; + g_debug ("DESTROY"); /* Ensure we call this on the main thread */ g_idle_add ((GSourceFunc) on_fuse_unmount, NULL); + + g_clear_pointer (&fuse_opts, g_free); } static struct fuse_lowlevel_ops xdp_fuse_oper = { @@ -3271,7 +3476,7 @@ typedef struct { } XdpFuseThreadData; static void -xdp_fuse_mainloop (struct fuse_session *se, +xdp_fuse_mainloop (struct fuse_session *se, struct fuse_loop_config *loop_config) { const char *status; @@ -3305,13 +3510,14 @@ xdp_fuse_thread (gpointer data) }; g_auto(XdpAutoFuseArgs) args = FUSE_ARGS_INIT (G_N_ELEMENTS (fusermount_argv), fusermount_argv); - g_autoptr(GMutexLocker) locker = NULL; g_autoptr(GMutexLocker) session_locker = NULL; - const char *path; - struct fuse_session *se; - XdpFuseThreadData *thread_data = data; + g_autoptr(GMutexLocker) locker = NULL; struct fuse_cmdline_opts opts = {0}; struct fuse_loop_config loop_config = {0}; + XdpFuseThreadData *thread_data = data; + XdpFuseOptions *fuse_opts = NULL; + struct fuse_session *se; + const char *path; locker = g_mutex_locker_new (&thread_data->lock); fuse_pthread = pthread_self (); @@ -3326,13 +3532,19 @@ xdp_fuse_thread (gpointer data) return NULL; } + fuse_opts = g_new0 (XdpFuseOptions, 1); +#ifdef WITH_SPLICE + fuse_opts->use_splice = TRUE; +#endif + se = fuse_session_new (&args, &xdp_fuse_oper, - sizeof (xdp_fuse_oper), NULL); + sizeof (xdp_fuse_oper), fuse_opts); if (se == NULL) { g_set_error (&thread_data->error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_FAILED, "Can't create fuse session"); + g_clear_pointer (&fuse_opts, g_free); return NULL; } @@ -3369,19 +3581,20 @@ xdp_fuse_thread (gpointer data) gboolean xdp_fuse_init (GError **error) { + g_autoptr(XdpDomain) by_app_domain = NULL; + g_autoptr(XdpDomain) root_domain = NULL; XdpFuseThreadData thread_data = {0}; - struct stat st; struct statfs stfs; - const char *path; struct rlimit rl; + struct stat st; + const char *path; int statfs_res; - g_autoptr(XdpDomain) root_domain = NULL; - g_autoptr(XdpDomain) by_app_domain = NULL; my_uid = getuid (); my_gid = getgid (); all_inodes = g_hash_table_new_full (g_int64_hash, g_int64_equal, NULL, NULL); + g_assert (open_files == NULL); root_domain = xdp_domain_new_root (); root_inode = xdp_inode_new (root_domain, NULL); @@ -3446,6 +3659,10 @@ xdp_fuse_init (GError **error) g_assert (session != NULL); + XDP_AUTOLOCK (open_files); + while (open_files) + xdp_file_free (open_files->data); + return TRUE; } @@ -3481,9 +3698,9 @@ typedef struct { /* Called with domain_inodes lock held, don't block */ static void -invalidate_doc_inode (XdpInode *parent_inode, +invalidate_doc_inode (XdpInode *parent_inode, const char *doc_id, - GArray *invalidates) + GArray *invalidates) { XdpInode *doc_inode = g_hash_table_lookup (parent_inode->domain->inodes, doc_id); Invalidate inval; @@ -3558,8 +3775,9 @@ xdp_fuse_invalidate_doc_app (const char *doc_id, } char * -xdp_fuse_lookup_id_for_inode (ino_t ino, gboolean directory, - char **real_path_out) +xdp_fuse_lookup_id_for_inode (ino_t ino, + gboolean directory, + char **real_path_out) { g_autoptr(XdpDomain) domain = NULL; g_autoptr(XdpPhysicalInode) physical = NULL; @@ -3574,7 +3792,7 @@ xdp_fuse_lookup_id_for_inode (ino_t ino, gboolean directory, XdpInode *inode = g_hash_table_lookup (all_inodes, &ino); if (inode) { - /* We're not allowed to ressurect the inode here, but we can get the data while in the lock */ + /* We're not allowed to resurrect the inode here, but we can get the data while in the lock */ domain = xdp_domain_ref (inode->domain); if (inode->physical) physical = xdp_physical_inode_ref (inode->physical); @@ -3618,29 +3836,8 @@ xdp_fuse_lookup_id_for_inode (ino_t ino, gboolean directory, return g_strdup (domain->doc_id); /* But maybe its a subfile of the document */ - if (real_path_out) - { - g_autofree char *fd_path = fd_to_path (physical->fd); - char path_buffer[PATH_MAX + 1]; - DevIno file_devino = physical->backing_devino; - ssize_t symlink_size; - struct stat buf; - - /* Try to extract a real path to the file (and verify it goes to the same place as the fd) */ - symlink_size = readlink (fd_path, path_buffer, PATH_MAX); - if (symlink_size >= 1) - { - path_buffer[symlink_size] = 0; - - if (lstat (path_buffer, &buf) == 0 && - buf.st_dev == file_devino.dev && - buf.st_ino == file_devino.ino) - { - *real_path_out = g_strdup (path_buffer); - return g_strdup (domain->doc_id); - } - } - } + if (real_path_out && xdp_fuse_get_real_path (physical, real_path_out)) + return g_strdup (domain->doc_id); } return NULL; diff --git a/document-portal/document-portal.c b/document-portal/document-portal.c index ccbb514..f3bd2ba 100644 --- a/document-portal/document-portal.c +++ b/document-portal/document-portal.c @@ -2,10 +2,12 @@ * Copyright © 2018 Red Hat, Inc * Copyright © 2023 GNOME Foundation Inc. * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -37,6 +39,7 @@ #include "glib-backports.h" #include "document-portal-dbus.h" #include "document-store.h" +#include "src/xdp-app-info.h" #include "src/xdp-utils.h" #include "permission-db.h" #include "permission-store-dbus.h" @@ -379,7 +382,7 @@ validate_fd (int fd, g_autofree char *path = NULL; g_autofree char *dirname = NULL; g_autofree char *name = NULL; - xdp_autofd int dir_fd = -1; + g_autofd int dir_fd = -1; struct stat real_st_buf; g_autoptr(GError) local_error = NULL; @@ -521,27 +524,6 @@ portal_add (GDBusMethodInvocation *invocation, g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", ids[0])); } -static char * -get_output (GError **error, - const char *argv0, - ...) -{ - gboolean res; - g_autofree char *output = NULL; - va_list ap; - - va_start (ap, argv0); - res = xdp_spawn (NULL, &output, 0, error, argv0, ap); - va_end (ap); - - if (res) - { - g_strchomp (output); - return g_steal_pointer (&output); - } - return NULL; -} - /* out => 0 == hidden 1 == read-only @@ -658,16 +640,18 @@ app_has_file_access (const char *target_app_id, if (g_str_has_prefix (target_app_id, "snap.")) { - res = get_output (&error, "snap", "routine", "file-access", + res = xdp_spawn (&error, "snap", "routine", "file-access", target_app_id + strlen ("snap."), path, NULL); } else { /* First we try flatpak info --file-access=PATH APPID, which is supported on new versions */ arg = g_strdup_printf ("--file-access=%s", path); - res = get_output (&error, "flatpak", "info", arg, target_app_id, NULL); + res = xdp_spawn (&error, "flatpak", "info", arg, target_app_id, NULL); } + g_strchomp (res); + if (res) { if (strcmp (res, "read-write") == 0) @@ -1194,7 +1178,7 @@ handle_method (GCallback method_callback, g_autoptr(XdpAppInfo) app_info = NULL; PortalMethod portal_method = (PortalMethod)method_callback; - app_info = xdp_invocation_lookup_app_info_sync (invocation, NULL, &error); + app_info = xdp_invocation_ensure_app_info_sync (invocation, NULL, &error); if (app_info == NULL) g_dbus_method_invocation_return_gerror (invocation, error); else @@ -1225,7 +1209,7 @@ portal_lookup (GDBusMethodInvocation *invocation, { const char *filename; g_autofree char *path = NULL; - xdp_autofd int fd = -1; + g_autofd int fd = -1; struct stat st_buf, real_dir_st_buf; g_autofree char *id = NULL; GError *error = NULL; @@ -1416,6 +1400,101 @@ portal_list (GDBusMethodInvocation *invocation, return TRUE; } +const char * +get_host_path_internal (GDBusMethodInvocation *invocation, + XdpAppInfo *app_info, + const char *id, + GError **error) +{ + g_autoptr(PermissionDbEntry) entry = NULL; + + XDP_AUTOLOCK (db); + + entry = permission_db_lookup (db, id); + + if (!entry) + { + if (error != NULL && *error == NULL) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Invalid ID passed (%s)", id); + } + + return NULL; + } + + if (!xdp_app_info_is_host (app_info)) + { + g_autofree const char **apps = NULL; + const char *app_id = NULL; + gboolean app_found = FALSE; + + app_id = xdp_app_info_get_id (app_info); + + apps = permission_db_entry_list_apps (entry); + for (size_t i = 0; apps[i] != NULL; i++) + { + if (g_strcmp0 (app_id, apps[i]) == 0) + { + app_found = TRUE; + break; + } + } + + if (!app_found) + { + if (error != NULL && *error == NULL) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "Not enough permissions"); + } + + return NULL; + } + } + + return document_entry_get_path (entry); +} + +static gboolean +portal_get_host_paths (GDBusMethodInvocation *invocation, + GVariant *parameters, + XdpAppInfo *app_info) +{ + g_autofree const char **id_list = NULL; + GVariantBuilder builder; + + g_variant_get (parameters, "(^a&s)", &id_list); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a{say})")); + g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{say}")); + + for (size_t i = 0; id_list[i] != NULL; i++) + { + g_autoptr(GError) error = NULL; + const char *path = NULL; + + path = get_host_path_internal (invocation, app_info, id_list[i], &error); + if (path == NULL) + { + g_warning ("Failed to get host path for %s: %s", id_list[i], error->message); + continue; + } + + g_variant_builder_add (&builder, "{s@ay}", id_list[i], g_variant_new_bytestring (path)); + } + + g_variant_builder_close (&builder); + + g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder)); + + return TRUE; +} + static void peer_died_cb (const char *name) { @@ -1432,7 +1511,7 @@ on_bus_acquired (GDBusConnection *connection, dbus_api = xdp_dbus_documents_skeleton_new (); - xdp_dbus_documents_set_version (XDP_DBUS_DOCUMENTS (dbus_api), 4); + xdp_dbus_documents_set_version (XDP_DBUS_DOCUMENTS (dbus_api), 5); g_signal_connect_swapped (dbus_api, "handle-get-mount-point", G_CALLBACK (handle_get_mount_point), NULL); g_signal_connect_swapped (dbus_api, "handle-add", G_CALLBACK (handle_method), portal_add); @@ -1445,6 +1524,7 @@ on_bus_acquired (GDBusConnection *connection, g_signal_connect_swapped (dbus_api, "handle-lookup", G_CALLBACK (handle_method), portal_lookup); g_signal_connect_swapped (dbus_api, "handle-info", G_CALLBACK (handle_method), portal_info); g_signal_connect_swapped (dbus_api, "handle-list", G_CALLBACK (handle_method), portal_list); + g_signal_connect_swapped (dbus_api, "handle-get-host-paths", G_CALLBACK (handle_method), portal_get_host_paths); file_transfer = file_transfer_create (); g_dbus_interface_skeleton_set_flags (file_transfer, @@ -1616,15 +1696,23 @@ message_handler (const gchar *log_domain, { /* Make this look like normal console output */ if (log_level & G_LOG_LEVEL_DEBUG) - printf ("XDP: %s\n", message); + fprintf (stderr, "XDP: %s\n", message); else - printf ("%s: %s\n", g_get_prgname (), message); + fprintf (stderr, "%s: %s\n", g_get_prgname (), message); } static void printerr_handler (const gchar *string) { - fprintf (stderr, "error: %s\n", string); + int is_tty = isatty (1); + const char *prefix = ""; + const char *suffix = ""; + if (is_tty) + { + prefix = "\x1b[31m\x1b[1m"; /* red, bold */ + suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */ + } + fprintf (stderr, "%serror: %s%s\n", prefix, suffix, string); } int @@ -1639,6 +1727,21 @@ main (int argc, g_autoptr(GOptionContext) context = NULL; GDBusMethodInvocation *invocation; + if (g_getenv ("XDG_DOCUMENT_PORTAL_WAIT_FOR_DEBUGGER") != NULL) + { + g_printerr ("document portal (PID %d) is waiting for a debugger. " + "Use `gdb -p %d` to connect. \n", + getpid (), getpid ()); + + if (raise (SIGSTOP) == -1) + { + g_printerr ("Failed waiting for debugger\n"); + exit (1); + } + + raise (SIGCONT); + } + g_log_writer_default_set_use_stderr (TRUE); setlocale (LC_ALL, ""); diff --git a/document-portal/document-portal.h b/document-portal/document-portal.h index 3203c72..766f13a 100644 --- a/document-portal/document-portal.h +++ b/document-portal/document-portal.h @@ -1,10 +1,12 @@ /* * Copyright © 2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/document-portal/document-store.h b/document-portal/document-store.h index 91d1411..c535feb 100644 --- a/document-portal/document-store.h +++ b/document-portal/document-store.h @@ -3,6 +3,7 @@ #include #include "permission-db.h" #include "document-enums.h" +#include "src/xdp-app-info.h" #include "src/xdp-utils.h" G_BEGIN_DECLS diff --git a/document-portal/file-transfer.c b/document-portal/file-transfer.c index 47068e4..a49c175 100644 --- a/document-portal/file-transfer.c +++ b/document-portal/file-transfer.c @@ -2,10 +2,12 @@ * Copyright © 2018 Red Hat, Inc * Copyright © 2023 GNOME Foundation Inc. * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -37,6 +39,7 @@ #include #include "file-transfer.h" +#include "src/xdp-app-info.h" #include "src/xdp-utils.h" #include "document-portal-dbus.h" #include "document-enums.h" @@ -98,10 +101,10 @@ file_transfer_finalize (GObject *object) FileTransfer *transfer = (FileTransfer *)object; g_mutex_clear (&transfer->mutex); - xdp_app_info_unref (transfer->app_info); - g_ptr_array_unref (transfer->files); - g_free (transfer->key); - g_free (transfer->sender); + g_clear_object (&transfer->app_info); + g_clear_pointer (&transfer->files, g_ptr_array_unref); + g_clear_pointer (&transfer->key, g_free); + g_clear_pointer (&transfer->sender, g_free); G_OBJECT_CLASS (file_transfer_parent_class)->finalize (object); } @@ -162,7 +165,7 @@ file_transfer_start (XdpAppInfo *app_info, transfer = g_object_new (file_transfer_get_type (), NULL); - transfer->app_info = xdp_app_info_ref (app_info); + transfer->app_info = g_object_ref (app_info); transfer->sender = g_strdup (sender); transfer->writable = writable; transfer->autostop = autostop; @@ -174,7 +177,7 @@ file_transfer_start (XdpAppInfo *app_info, g_free (transfer->key); key = g_random_int (); key = (key << 32) | g_random_int (); - transfer->key = g_strdup_printf ("%lu", key); + transfer->key = g_strdup_printf ("%" G_GUINT64_FORMAT, key); } while (g_hash_table_contains (transfers, transfer->key)); g_hash_table_insert (transfers, transfer->key, g_object_ref (transfer)); @@ -524,7 +527,7 @@ handle_method (GCallback method_callback, g_autoptr(XdpAppInfo) app_info = NULL; PortalMethod portal_method = (PortalMethod)method_callback; - app_info = xdp_invocation_lookup_app_info_sync (invocation, NULL, &error); + app_info = xdp_invocation_ensure_app_info_sync (invocation, NULL, &error); if (app_info == NULL) g_dbus_method_invocation_return_gerror (invocation, error); else diff --git a/document-portal/file-transfer.h b/document-portal/file-transfer.h index 7e0a415..7cefa4e 100644 --- a/document-portal/file-transfer.h +++ b/document-portal/file-transfer.h @@ -1,10 +1,12 @@ /* * Copyright © 2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/document-portal/meson.build b/document-portal/meson.build index 2f13437..e430f17 100644 --- a/document-portal/meson.build +++ b/document-portal/meson.build @@ -3,7 +3,6 @@ permission_store_built_sources = gnome.gdbus_codegen( sources: '../data/org.freedesktop.impl.portal.PermissionStore.xml', interface_prefix: 'org.freedesktop.impl.portal', namespace: 'Xdg', - autocleanup: 'none', ) db_sources = files( @@ -46,7 +45,6 @@ document_portal_built_sources = gnome.gdbus_codegen( ], interface_prefix: 'org.freedesktop.portal', namespace: 'XdpDbus', - autocleanup: 'none', ) xdg_document_portal_sources = [ diff --git a/document-portal/permission-db.c b/document-portal/permission-db.c index 492443f..493b351 100644 --- a/document-portal/permission-db.c +++ b/document-portal/permission-db.c @@ -2,9 +2,11 @@ * * Copyright (C) 2015 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This file is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2 of the + * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This file is distributed in the hope that it will be useful, but @@ -300,7 +302,7 @@ initable_init (GInitable *initable, } else { - g_propagate_error (error, my_error); + g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } } @@ -768,7 +770,7 @@ permission_db_update (PermissionDb *self) /* We should never list an app that has empty id lists */ g_assert (app_ids[0] != NULL); - g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); for (j = 0; app_ids[j] != NULL; j++) g_variant_builder_add (&builder, "s", app_ids[j]); @@ -838,7 +840,7 @@ save_content_callback (GObject *source_object, if (ok) g_task_return_boolean (task, TRUE); else - g_task_return_error (task, error); + g_task_return_error (task, g_steal_pointer (&error)); } void @@ -1142,9 +1144,7 @@ add_permissions (GVariant *app_permissions, const char *new_app_id; const char *child_app_id; - g_autoptr(GVariant) new_perms_array = NULL; - - g_variant_get (permissions, "{&s@as}", &new_app_id, &new_perms_array); + g_variant_get (permissions, "{&s@as}", &new_app_id, NULL); g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); @@ -1196,14 +1196,12 @@ remove_permissions (GVariant *app_permissions, GVariant *child; const char *child_app_id; - g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sas}")); g_variant_iter_init (&iter, app_permissions); while ((child = g_variant_iter_next_value (&iter))) { - g_autoptr(GVariant) old_perms_array = NULL; - - g_variant_get (child, "{&s@as}", &child_app_id, &old_perms_array); + g_variant_get (child, "{&s@as}", &child_app_id, NULL); if (strcmp (app, child_app_id) != 0) g_variant_builder_add_value (&builder, child); diff --git a/document-portal/permission-db.h b/document-portal/permission-db.h index 6c02a07..f74779d 100644 --- a/document-portal/permission-db.h +++ b/document-portal/permission-db.h @@ -2,9 +2,11 @@ * * Copyright © 2015 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This file is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2 of the + * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This file is distributed in the hope that it will be useful, but diff --git a/document-portal/permission-store.c b/document-portal/permission-store.c index e1106d3..be25ef6 100644 --- a/document-portal/permission-store.c +++ b/document-portal/permission-store.c @@ -92,6 +92,21 @@ main (int argc, GOptionContext *context; g_autoptr(GError) error = NULL; + if (g_getenv ("XDG_PERMISSION_STORE_WAIT_FOR_DEBUGGER") != NULL) + { + g_printerr ("\npermission store (PID %d) is waiting for a debugger. " + "Use `gdb -p %d` to connect. \n", + getpid (), getpid ()); + + if (raise (SIGSTOP) == -1) + { + g_printerr ("Failed waiting for debugger\n"); + exit (1); + } + + raise (SIGCONT); + } + g_log_writer_default_set_use_stderr (TRUE); setlocale (LC_ALL, ""); diff --git a/document-portal/xdg-document-portal.service.in b/document-portal/xdg-document-portal.service.in index 2c90589..d10fdbe 100644 --- a/document-portal/xdg-document-portal.service.in +++ b/document-portal/xdg-document-portal.service.in @@ -1,6 +1,8 @@ [Unit] Description=flatpak document portal service PartOf=graphical-session.target +Requires=dbus.service +After=dbus.service [Service] BusName=org.freedesktop.portal.Documents diff --git a/document-portal/xdg-permission-store.service.in b/document-portal/xdg-permission-store.service.in index e26c2b9..66fe6d5 100644 --- a/document-portal/xdg-permission-store.service.in +++ b/document-portal/xdg-permission-store.service.in @@ -1,6 +1,8 @@ [Unit] Description=sandboxed app permission store PartOf=graphical-session.target +Requires=dbus.service +After=dbus.service [Service] BusName=org.freedesktop.impl.portal.PermissionStore diff --git a/meson.build b/meson.build index c1ea053..d31b316 100644 --- a/meson.build +++ b/meson.build @@ -1,16 +1,20 @@ project( 'xdg-desktop-portal', 'c', - version: '1.18.4', - meson_version: '>= 0.58', + version: '1.20.3', + meson_version: '>= 0.60', license: 'LGPL-2.0-or-later', - default_options: ['warning_level=2']) + default_options: [ + 'warning_level=2', + ], +) ###### various directories we'll use later # foodir are built-in ones, foo_dir are our options prefix = get_option('prefix') datadir = prefix / get_option('datadir') +libdir = prefix / get_option('libdir') libexecdir = prefix / get_option('libexecdir') sysconfdir = prefix / get_option('sysconfdir') localedir = prefix / get_option('localedir') @@ -64,6 +68,7 @@ src_includes = include_directories('src') i18n = import('i18n') gnome = import('gnome') pkgconfig = import('pkgconfig') +pymod = import('python') cc = meson.get_compiler('c') cflags = [ @@ -85,6 +90,9 @@ config_h.set_quoted('PACKAGE_STRING', 'xdg-desktop-portal @0@'.format(meson.proj if cc.has_function('renameat2') config_h.set('HAVE_RENAMEAT2', 1) endif +if cc.has_function('splice') + config_h.set('HAVE_SPLICE', 1) +endif check_headers = [ ['sys/vfs.h', 'HAVE_SYS_VFS_H'], @@ -98,39 +106,55 @@ foreach h : check_headers config_h.set(h.get(1), cc.has_header(h.get(0))) endforeach -glib_dep = dependency('glib-2.0', version: '>= 2.66') +glib_dep = dependency('glib-2.0', version: '>= 2.72') gio_dep = dependency('gio-2.0') gio_unix_dep = dependency('gio-unix-2.0') json_glib_dep = dependency('json-glib-1.0') fuse3_dep = dependency('fuse3', version: '>= 3.10.0') gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0') -geoclue_dep = dependency('libgeoclue-2.0', - version: '>= 2.5.2', - required: get_option('geoclue')) -libportal_dep = dependency('libportal', - required: get_option('libportal')) +gst_pbutils_dep = dependency('gstreamer-pbutils-1.0') +geoclue_dep = dependency( + 'libgeoclue-2.0', + version: '>= 2.5.2', + required: get_option('geoclue'), +) pipewire_dep = dependency('libpipewire-0.3', version: '>= 0.2.90') libsystemd_dep = dependency('libsystemd', required: get_option('systemd')) +gudev_dep = dependency('gudev-1.0', required: get_option('gudev')) +umockdev_dep = dependency('umockdev-1.0', required: get_option('tests')) + +gst_inspect = find_program('gst-inspect-1.0', required: false) +if gst_inspect.found() + have_wav_parse = run_command( + gst_inspect, 'wavparse', '--exists', + check: false, + ).returncode() == 0 +else + have_wav_parse = false +endif +if have_wav_parse + config_h.set('HAVE_WAV_PARSE', 1) +endif +pytest = find_program('pytest-3', 'pytest', required: get_option('tests')) +python = pymod.find_installation( + 'python3', + modules: ['dbus', 'dbusmock', 'gi'], + required: get_option('tests'), +) -use_bwrap = get_option('sandboxed-image-validation') -bwrap = find_program('bwrap', required: use_bwrap) +bwrap = find_program('bwrap', required: get_option('sandboxed-image-validation').allowed() or get_option('sandboxed-sound-validation').allowed()) -if not use_bwrap +if not bwrap.found() warning(''' - Sandboxed image validation with Bubblewrap is DISABLED. + Sandboxed image and sound validation with Bubblewrap is DISABLED. If your system can run Bubblewrap, it's **hightly** recommended that you enable this - option. Bitmap validation and processing is a common attack vector. - By proceeding with sandboxed image validation disabled, you acknowledge that you + option. Bitmap and sound validation and processing is a common attack vector. + By proceeding with sandboxed image and sound validation disabled, you acknowledge that you are lowering your system's security, and are subject to known or unknown exploits. ''') endif -have_libportal = libportal_dep.found() -if have_libportal - config_h.set('HAVE_LIBPORTAL', 1) -endif - have_geoclue = geoclue_dep.found() if have_geoclue config_h.set('HAVE_GEOCLUE', 1) @@ -141,24 +165,29 @@ if have_libsystemd config_h.set('HAVE_LIBSYSTEMD', 1) endif -add_project_arguments(['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_66'], language: 'c') +have_gudev = gudev_dep.found() +if have_gudev + config_h.set('HAVE_GUDEV', 1) +endif + +add_project_arguments(['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_72'], language: 'c') -build_docbook = false -xmlto = find_program('xmlto', required: get_option('docbook-docs')) -if xmlto.found() +build_documentation = false +gdbus_codegen = find_program('gdbus-codegen', native: true, required: get_option('documentation')) +sphinx_build = find_program('sphinx-build', native: true, required: get_option('documentation')) +sphinx_modules = pymod.find_installation('python3', modules: ['sphinxext.opengraph', 'sphinx_copybutton', 'furo'], required: get_option('documentation')) +if not get_option('documentation').disabled() and gdbus_codegen.found() and sphinx_build.found() and sphinx_modules.found() fs = import('fs') # we're going to copy this file in to our build tree if fs.is_file(flatpak_intf_dir / 'org.freedesktop.portal.Flatpak.xml') - build_docbook = true - elif get_option('docbook-docs').enabled() + build_documentation = true + elif get_option('documentation').enabled() error('Flatpak development files are required to build DocBook docs') endif endif rst2man = find_program('rst2man', 'rst2man.py', required: get_option('man-pages')) -enable_installed_tests = get_option('installed-tests') - ###### systemd units, dbus service files, pkgconfig base_config = configuration_data() @@ -182,26 +211,42 @@ pkgconfig.generate( }, ) +###### subdirs + subdir('data') subdir('src') subdir('document-portal') -subdir('tests') subdir('po') subdir('doc') +enable_tests = get_option('tests') \ + .require(pytest.found()) \ + .require(python.found() and python.language_version().version_compare('>=3.9'), + error_message: 'Python version >=3.9 is required') \ + .require(umockdev_dep.found()) \ + .require(have_wav_parse, + error_message: 'gst-inspect and the wavparse plugins are required') \ + .allowed() + +enable_installed_tests = get_option('installed-tests') + +if enable_tests + subdir('tests') +endif + ###### generate config.h configure_file(output: 'config.h', configuration: config_h) summary({ - 'Enable docbook documentation': build_docbook, + 'Enable documentation': build_documentation, 'Enable libsystemd support': have_libsystemd, 'Enable geoclue support': have_geoclue, - 'Enable libportal support': have_libportal, + 'Enable gudev support': have_gudev, + 'Enable test suite': enable_tests, 'Enable installed tests:': enable_installed_tests, - 'Enable python test suite': enable_pytest, 'Build man pages': rst2man.found(), 'Build flatpak interfaces': flatpak_intf_dir != '', - 'Sandboxed image validation': use_bwrap, + 'Sandboxed image validation': bwrap.found(), }, section: 'Optional builds', bool_yn: true, diff --git a/meson_options.txt b/meson_options.txt index 41b2eef..ed8c311 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -14,43 +14,43 @@ option('systemd-user-unit-dir', type: 'string', value: '', description: 'directory for systemd user service files (default: PREFIX/lib/systemd/user)') -option('libportal', - type: 'feature', - value: 'auto', - description: 'Enable libportal support. Needed for tests') option('geoclue', type: 'feature', value: 'auto', description: 'Enable Geoclue support. Needed for location portal') +option('gudev', + type: 'feature', + value: 'auto', + description: 'Enable udev support. Needed for the USB portal.') option('systemd', type: 'feature', value: 'auto', description: 'Enable systemd support') -option('docbook-docs', +option('documentation', type: 'feature', value: 'auto', - description: 'Build documentation (requires xmlto)') -option('xmlto-flags', - type: 'array', - value: [], - description: 'Define/override "xmlto" options, like "--skip-validation"') + description: 'Build documentation (requires sphinx)') option('datarootdir', type: 'string', value: '', description: 'Define the datarootdir for the pkgconf file') +option('tests', + type: 'feature', + value: 'enabled', + description: 'Enable the test suite') option('installed-tests', type: 'boolean', value: false, description: 'Enable installation of some test cases') -option('pytest', - type: 'feature', - value: 'auto', - description: 'Enable the pytest-based test suite') option('man-pages', type: 'feature', value: 'auto', description: 'Build man pages (requires rst2man)') option('sandboxed-image-validation', - type: 'boolean', - value: true, + type: 'feature', + value: 'enabled', description: 'Use Bubblewrap to sandbox image validation. Disabling this option may lead to security vulnerabilities.') +option('sandboxed-sound-validation', + type: 'feature', + value: 'enabled', + description: 'Use Bubblewrap to sandbox sound validation. Disabling this option may lead to security vulnerabilities.') diff --git a/po/LINGUAS b/po/LINGUAS index 9c847f6..53a6407 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1,4 +1,5 @@ be +bg ca cs da @@ -12,7 +13,9 @@ hi hr hu id +ie it +ja ka lt nl @@ -23,6 +26,7 @@ pt_BR ro ru sk +sl sr sv tr diff --git a/po/POTFILES.in b/po/POTFILES.in index 5729dfe..d514b21 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,7 +1,9 @@ # List of source files containing translatable strings. src/background.c -src/device.c +src/camera.c +src/dynamic-launcher.c src/location.c src/screenshot.c src/settings.c +src/usb.c src/wallpaper.c diff --git a/po/be.po b/po/be.po index 7e01277..62d6abb 100644 --- a/po/be.po +++ b/po/be.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal main\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2023-01-04 01:42+0300\n" "Last-Translator: Yuras Shumovich \n" "Language-Team: Belarusian \n" @@ -19,167 +19,258 @@ msgstr "" "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 3.2.2\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Дазволіць %s выкананне ў фоне?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s хоча атрымаць доступ на аўтаматычны запуск і выкананне ў фоне." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s хоча атрымаць доступ на выкананне ў фоне." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." msgstr "" "Дазвол на «выкананне ў фоне» заўсёды можна змяніць праз налады праграм." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Не дазваляць" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Дазволіць" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Уключыць мікрафон?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Дазволіць %s выкананне ў фоне?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "Доступ да мікрафона заўсёды можна змяніць праз налады прыватнасці." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s хоча атрымаць доступ да камеры." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "" + +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Праграма хоча атрымаць доступ да камеры." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Праграма хоча атрымаць доступ да мікрафона." +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:123 +#: src/dynamic-launcher.c:133 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s хоча атрымаць доступ да мікрафона." +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Уключыць дынамікі?" +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." -msgstr "Доступ да дынамікаў заўсёды можна змяніць праз налады прыватнасці." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Праграма хоча атрымаць доступ на прайграванне гуку." +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:400 #, c-format -msgid "%s wants to play sound." -msgstr "%s хоча атрымаць доступ на прайграванне гуку." +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Уключыць камеру?" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "Доступ да камеры заўсёды можна змяніць праз налады прыватнасці." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Праграма хоча атрымаць доступ да камеры." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s хоча атрымаць доступ да камеры." +msgid "Unsupported launcher type: %x" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Забараніць доступ" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Дазволіць доступ" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Даць %s доступ да вашага месцазнаходжання?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s хоча атрымаць доступ да месцазнаходжання." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Дазволіць доступ да вашага месцазнаходжання?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Праграма хоча атрымаць доступ да месцазнаходжання." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Доступ да месцазнаходжанне заўсёды можна змяніць праз налады прыватнасці." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Забараніць" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Дазволіць %s рабіць здымкі экрана?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s хоча атрымаць доступ у любы час рабіць здымкі экрана." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Дазволіць праграмам рабіць здымкі экрана?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Праграма хоча атрымаць доступ у любы час рабіць здымкі экрана." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Доступ да месцазнаходжанне заўсёды можна змяніць праз налады прыватнасці." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Запытаная налада не знойдзена" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Не дазваляць" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Дазволіць %s змяняць фон?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s хоча атрымаць доступ на змяненне фонавай выявы." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Дазволіць праграмам змяняць фон?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Праграма запытвае доступ на змяненне фонавай выявы." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Уключыць мікрафон?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "Доступ да мікрафона заўсёды можна змяніць праз налады прыватнасці." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Праграма хоча атрымаць доступ да мікрафона." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s хоча атрымаць доступ да мікрафона." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Уключыць дынамікі?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "Доступ да дынамікаў заўсёды можна змяніць праз налады прыватнасці." + +#~ msgid "An application wants to play sound." +#~ msgstr "Праграма хоча атрымаць доступ на прайграванне гуку." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s хоча атрымаць доступ на прайграванне гуку." + +#~ msgid "Turn On Camera?" +#~ msgstr "Уключыць камеру?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "Доступ да камеры заўсёды можна змяніць праз налады прыватнасці." diff --git a/po/bg.po b/po/bg.po new file mode 100644 index 0000000..6360de0 --- /dev/null +++ b/po/bg.po @@ -0,0 +1,243 @@ +# Bulgarian translation of xdg-desktop-portal po-file. +# Copyright (C) 2024 xdg-desktop-portal's COPYRIGHT HOLDER +# This file is distributed under the same license as the xdg-desktop-portal package. +# twlvnn kraftwerk , 2024, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: xdg-desktop-portal main\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2025-02-22 16:52+0100\n" +"Last-Translator: twlvnn kraftwerk \n" +"Language-Team: Bulgarian \n" +"Language: bg\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: Gtranslator 47.1\n" + +#: src/background.c:827 +#, c-format +msgid "Allow %s to run in the background?" +msgstr "Позволявате ли на „%s“ да работи във фонов режим?" + +#: src/background.c:831 +#, c-format +msgid "%s requests to be started automatically and run in the background." +msgstr "Заявка от „%s“ да се стартира автоматично и да работи във фонов режим." + +#: src/background.c:833 +#, c-format +msgid "%s requests to run in the background." +msgstr "„%s“ иска да се изпълнява във фонов режим." + +#: src/background.c:834 +msgid "" +"The ‘run in background’ permission can be changed at any time from the " +"application settings." +msgstr "" +"Правото за „работа във фонов режим“ може да бъде променено по всяко време от " +"настройките на програмата." + +#: src/background.c:838 +msgid "Don't allow" +msgstr "Забраняване" + +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 +msgid "Allow" +msgstr "Позволяване" + +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Позволяване на „%s“ да използва камерата?" + +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "Заявка от „%s“ да ползва камерите." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Позволявате ли на програмата да ползва камерата?" + +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Програма иска достъп до камерите." + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Идентификаторът на файла за работен плот нe завършва на „.desktop“: %s" + +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "Идентификаторът на файла за работния плот е неправилен" + +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" +"Записът за работния плот, зададен за „Install()“, е неправилен ключов файл" + +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" +"Записът за работния плот, зададен за „Install()“, трябва да има само една " +"група" + +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "Даденият токен е неправилен: %s" + +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "Записът за работния плот, зададен за „Install()“, е неправилен" + +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Файлът за работния плот надвишава максималния размер (%d): %s" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "Даденият адрес е неправилен: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Неправилен вид стартер: %x" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "Неподдържан вид стартер: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Динамичната иконка на стартера не може да се одобри" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" +"Програмата с идентификатор %s няма достъп до функцията " +"„RequestInstallToken()“" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Иконката на файла „%s“ за работния плот е на неразпознат път" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Иконката на файла „%s“ за работния плот не може да бъде сериализирана" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Не съществува динамичен стартер с идентификатор „%s“" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" +"Неуспешно създаване на GDesktopAppInfo за стартера с идентификатор „%s“" + +#: src/location.c:544 +msgid "Deny Access" +msgstr "Отказване на достъп" + +#: src/location.c:546 +msgid "Grant Access" +msgstr "Позволяване на достъп" + +#: src/location.c:566 +#, c-format +msgid "Give %s Access to Your Location?" +msgstr "Позволявате ли на „%s“ достъп до местоположение ви?" + +#: src/location.c:571 +#, c-format +msgid "%s wants to use your location." +msgstr "Заявка от „%s“ за местоположението ви." + +#: src/location.c:580 +msgid "Grant Access to Your Location?" +msgstr "Позволявате ли достъп до местоположение ви?" + +#: src/location.c:581 +msgid "An application wants to use your location." +msgstr "Програма иска достъп до местоположението ви." + +#: src/location.c:584 +msgid "Location access can be changed at any time from the privacy settings." +msgstr "" +"Може да промените правата за достъп до местоположението през настройките за " +"лични данни." + +#: src/screenshot.c:245 src/wallpaper.c:185 +msgid "Deny" +msgstr "Забраняване" + +#: src/screenshot.c:267 +#, c-format +msgid "Allow %s to Take Screenshots?" +msgstr "Позволявате ли на „%s“ да прави снимки на екрана?" + +#: src/screenshot.c:268 +#, c-format +msgid "%s wants to be able to take screenshots at any time." +msgstr "„%s“ иска да може да прави снимки на екрана по всяко време." + +#: src/screenshot.c:276 +msgid "Allow Applications to Take Screenshots?" +msgstr "Позволявате ли на програми да правят снимки на екрана?" + +#: src/screenshot.c:277 +msgid "An application wants to be able to take screenshots at any time." +msgstr "Програма иска да може да прави снимки на екрана по всяко време." + +#: src/screenshot.c:280 src/wallpaper.c:220 +msgid "This permission can be changed at any time from the privacy settings." +msgstr "Може да промените правата за достъп през настройките за лични данни." + +#: src/settings.c:187 src/settings.c:223 +msgid "Requested setting not found" +msgstr "Поисканата настройка липсва" + +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Забраняване" + +#: src/wallpaper.c:207 +#, c-format +msgid "Allow %s to Set Backgrounds?" +msgstr "Позволявате ли на „%s“ да задава фонове?" + +#: src/wallpaper.c:208 +#, c-format +msgid "%s is requesting to be able to change the background image." +msgstr "„%s“ иска да може да променя изображението на фона." + +#: src/wallpaper.c:217 +msgid "Allow Applications to Set Backgrounds?" +msgstr "Позволявате ли на програми да задават фонове?" + +#: src/wallpaper.c:218 +msgid "An application is requesting to be able to change the background image." +msgstr "Програма иска да може да променя изображението на фона." diff --git a/po/ca.po b/po/ca.po index b5da357..6f074db 100644 --- a/po/ca.po +++ b/po/ca.po @@ -2,14 +2,14 @@ # Copyright (C) 2021 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. # Maite Guix , 2021. -# Jordi Mas i Hernàndez , 2023 +# Jordi Mas i Hernàndez , 2023-2025 # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2021-11-10 19:00+0100\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2025-10-03 19:00+0100\n" "Last-Translator: Jordi Mas i Hernàndez \n" "Language-Team: Catalan \n" "Language: ca\n" @@ -18,23 +18,23 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.0\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Voleu que %s s'executi en segon pla?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "" "%s sol·licituds que s'inicien automàticament i s'executen en segon pla." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s sol·licituds per a executar-se en segon pla." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -42,153 +42,202 @@ msgstr "" "El permís «executa en segon pla» es pot canviar en qualsevol moment des de " "configuració de l'aplicació." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "No ho permetis" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Permet" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Voleu activar el micròfon?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Voleu permetre que %s utilitzi la càmera?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"L'accés al micròfon es pot canviar en qualsevol moment des de la " -"configuració de privacitat." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s vol accedir als dispositius de la càmera." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Voleu permetre que l'aplicació utilitzi la càmera?" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Una aplicació vol utilitzar el micròfon." +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Una aplicació vol accedir a la càmera." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s vol utilitzar el micròfon." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Manca l'ID del fitxer d'escriptori .desktop sufix: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Voleu activar els altaveus?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "L'ID del fitxer d'escriptori no és vàlid" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"L'accés als altaveus es pot canviar en qualsevol moment des de la " -"configuració de privacitat." +"L'entrada d'escriptori donada a Install() no és un fitxer de clau vàlid" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Una aplicació vol reproduir so." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" +"L'entrada d'escriptori donada a Install() ha de tenir exactament un grup" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s vol reproduir so." +msgid "Token given is invalid: %s" +msgstr "El testimoni indicat no és vàlid: %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Voleu activar la càmera?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "L'entrada d'escriptori donada a Install() no és vàlida" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "" -"L'accés a la càmera es pot canviar en qualsevol moment des de la " -"configuració de privacitat." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "El fitxer d'escriptori supera la mida màxima (%d): %s" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "L'URL indicat no és vàlid: %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Una aplicació vol utilitzar la càmera." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Tipus de llançador no vàlid: %x" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s vol utilitzar la càmera." +msgid "Unsupported launcher type: %x" +msgstr "Tipus de llançador no admès: %x" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Ha fallat la validació llançador d'icones dinàmic" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() no està permès per a l'ID d'aplicació %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "La icona del fitxer d'escriptori «%s» al camí no reconegut" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "La icona del fitxer d'escriptori «%s» no s'ha pogut serialitzar" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "No hi ha cap llançador dinàmic amb l'ID «%s»" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" +"No s'ha pogut crear el GDesktopAppInfo per al llançador amb l'identificador " +"«%s»" + +#: src/location.c:544 msgid "Deny Access" msgstr "Denega l'accés" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Permet l'accés" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Permeteu que %s tingui accés a la vostra ubicació?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s vol saber la teva ubicació." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Permeteu l'accés a la vostra ubicació?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Una aplicació vol utilitzar la teva ubicació." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Podeu canviar la configuració de l'accés a la ubicació sempre que vulgueu " "des de la configuració de la privacitat." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Denega" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Voleu permetre que %s faci captures de pantalla?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s vol poder fer captures de pantalla en qualsevol moment." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Voleu permetre que les aplicacions facin captures de pantalla?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Una aplicació vol poder fer captures de pantalla en qualsevol moment." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Aquest permís es pot canviar en qualsevol moment des de la configuració de " "la privacitat." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "No s'ha trobat la configuració sol·licitada" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "El dispositiu no està disponible" + +#: src/usb.c:1342 +msgid "Not allowed" +msgstr "No permès" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Voleu permetre que %s defineixi els fons?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s demana poder canviar la imatge de fons." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Voleu permetre que les aplicacions estableixin el fons?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Una aplicació sol·licita poder canviar la imatge de fons." diff --git a/po/cs.po b/po/cs.po index b658700..b8fbc67 100644 --- a/po/cs.po +++ b/po/cs.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2022-09-20 23:38+0200\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2023-12-27 17:44+0100\n" "Last-Translator: Daniel Rusek \n" "Language-Team: Czech \n" "Language: cs\n" @@ -18,171 +18,289 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Poedit 3.1.1\n" +"X-Generator: Poedit 3.4.1\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Povolit aplikaci %s běh na pozadí?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "Aplikace %s požaduje automatické spouštění a běh na pozadí." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "Aplikace %s požaduje běh na pozadí." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." msgstr "" "Oprávnění pro „běh na pozadí“ můžete kdykoliv změnit v nastavení aplikace." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Nepovolit" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Povolit" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Zapnout mikrofon?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Povolit aplikaci %s používat kameru?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "Aplikace %s chce přistupovat ke kamerám." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Povolit aplikaci používat kameru?" + +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Nějaká aplikace chce přistupovat ke kamerám." + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" msgstr "" -"Přístup ke svému mikrofonu můžete kdykoliv změnit v nastavení soukromí." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Nějaká aplikace chce používat váš mikrofon." +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:123 +#: src/dynamic-launcher.c:306 #, c-format -msgid "%s wants to use your microphone." -msgstr "Aplikace %s chce používat váš mikrofon." +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Zapnout reproduktory?" +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" msgstr "" -"Přístup ke svým reproduktorům můžete kdykoliv změnit v nastavení soukromí." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Nějaká aplikace chce přehrávat zvuk." +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 #, c-format -msgid "%s wants to play sound." -msgstr "Aplikace %s chce přehrávat zvuk." +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Zapnout kameru?" +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "Přístup ke své kameře můžete kdykoliv změnit v nastavení soukromí." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Nějaká aplikace chce používat vaši kameru." +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:741 #, c-format -msgid "%s wants to use your camera." -msgstr "Aplikace %s chce používat vaši kameru." +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Zamítnout přístup" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Schválit přístup" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Schválit pro %s přístup k vaší poloze?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "Aplikace %s chce použít vaši polohu." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Schválit přístup k vaší poloze?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Nějaká aplikace chce použít vaši polohu." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Přístup ke službám pro určování polohy můžete kdykoliv změnit v nastavení " "soukromí." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Zamítnout" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Povolit aplikaci %s pořizovat snímky obrazovky?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "Aplikace %s chce mít možnost kdykoliv pořizovat snímky obrazovky." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Povolit aplikacím pořizovat snímky obrazovky?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Nějaká aplikace chce mít možnost kdykoliv pořizovat snímky obrazovky." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "Toto oprávnění můžete kdykoliv změnit v nastavení soukromí." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Požadované nastavení nebylo nalezeno" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Nepovolit" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Povolit aplikaci %s možnost nastavit pozadí?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "Aplikace %s požaduje mít možnost změnit obrázek na pozadí." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Povolit aplikacím možnost nastavit pozadí?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Aplikace požaduje mít možnost změnit obrázek na pozadí." + +#, c-format +#~ msgid "Allow %s to Use the Microphone?" +#~ msgstr "Povolit aplikaci %s používat mikrofon?" + +#, c-format +#~ msgid "%s wants to access recording devices." +#~ msgstr "Aplikace %s chce přistupovat k záznamovým zařízením." + +#~ msgid "Allow app to Use the Microphone?" +#~ msgstr "Povolit aplikaci používat mikrofon?" + +#~ msgid "An app wants to access recording devices." +#~ msgstr "Nějaká aplikace chce přistupovat k záznamovým zařízením." + +#, c-format +#~ msgid "Allow %s to Use the Speakers?" +#~ msgstr "Povolit aplikaci %s používat reproduktory?" + +#, c-format +#~ msgid "%s wants to access audio devices." +#~ msgstr "Aplikace %s chce přistupovat ke zvukovým zařízením." + +#~ msgid "Allow app to Use the Speakers?" +#~ msgstr "Povolit aplikaci používat reproduktory?" + +#~ msgid "An app wants to access audio devices." +#~ msgstr "Nějaká aplikace chce přistupovat ke zvukovým zařízením." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Zapnout mikrofon?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Přístup ke svému mikrofonu můžete kdykoliv změnit v nastavení soukromí." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Nějaká aplikace chce používat váš mikrofon." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "Aplikace %s chce používat váš mikrofon." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Zapnout reproduktory?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Přístup ke svým reproduktorům můžete kdykoliv změnit v nastavení soukromí." + +#~ msgid "An application wants to play sound." +#~ msgstr "Nějaká aplikace chce přehrávat zvuk." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "Aplikace %s chce přehrávat zvuk." + +#~ msgid "Turn On Camera?" +#~ msgstr "Zapnout kameru?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "Přístup ke své kameře můžete kdykoliv změnit v nastavení soukromí." diff --git a/po/da.po b/po/da.po index 6ad4ce0..fee512c 100644 --- a/po/da.po +++ b/po/da.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2020-10-12 01:25+0200\n" "Last-Translator: scootergrisen\n" "Language-Team: Danish\n" @@ -16,22 +16,22 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Tillad %s at køre i baggrunden?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s anmoder om at starte automatisk og køre i baggrunden." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s anmoder om at køre i baggrunden." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -39,151 +39,244 @@ msgstr "" "‘Kør i baggrunden’-tilladelsen kan når som helst ændres i " "programindstillingerne." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Tillad ikke" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Tillad" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Tænd for mikrofonen?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Tillad %s at køre i baggrunden?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s anmoder om at bruge dit kamera." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"Adgang til din mikrofon kan når som helst ændres i privatlivsindstillingerne." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Et program anmoder om at bruge din mikrofon." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Et program anmoder om at bruge dit kamera." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s anmoder om at bruge din mikrofon." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Tænd for højttalerne?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" + +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" msgstr "" -"Adgang til dine højttalere kan når som helst ændres i " -"privatlivsindstillingerne." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Et program anmoder om at afspille lyd." +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:400 #, c-format -msgid "%s wants to play sound." -msgstr "%s anmoder om at afspille lyd." +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Tænd for kameraet?" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" msgstr "" -"Adgang til dit kamera kan når som helst ændres i privatlivsindstillingerne." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Et program anmoder om at bruge dit kamera." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s anmoder om at bruge dit kamera." +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Nægt adgang" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Giv adgang" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Giv %s adgang til din placering?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s anmoder om at bruge din placering." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Giv adgang til din placering?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Et program anmoder om at bruge din placering." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Adgang til din placering kan når som helst ændres i " "privatlivsindstillingerne." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Nægt" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "Tillad %s at indstille baggrunde?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Tillad programmer at indstille baggrunde?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Et program anmoder om at bruge din mikrofon." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "Tilladelsen kan når som helst ændres i privatlivsindstillingerne." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Den anmodede indstilling blev ikke fundet" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Tillad ikke" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Tillad %s at indstille baggrunde?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s anmoder om at være i stand til at ændre baggrundsbilledet." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Tillad programmer at indstille baggrunde?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Et program anmoder om at være i stand til at ændre baggrundsbilledet." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Tænd for mikrofonen?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Adgang til din mikrofon kan når som helst ændres i " +#~ "privatlivsindstillingerne." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Et program anmoder om at bruge din mikrofon." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s anmoder om at bruge din mikrofon." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Tænd for højttalerne?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Adgang til dine højttalere kan når som helst ændres i " +#~ "privatlivsindstillingerne." + +#~ msgid "An application wants to play sound." +#~ msgstr "Et program anmoder om at afspille lyd." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s anmoder om at afspille lyd." + +#~ msgid "Turn On Camera?" +#~ msgstr "Tænd for kameraet?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Adgang til dit kamera kan når som helst ændres i " +#~ "privatlivsindstillingerne." diff --git a/po/de.po b/po/de.po index d1ccfcf..9a3b7ee 100644 --- a/po/de.po +++ b/po/de.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2016-09-23 19:17+0200\n" "Last-Translator: Mario Blättermann \n" "Language-Team: German \n" @@ -19,22 +19,22 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.8.9\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "" -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "" -#: src/background.c:754 +#: src/background.c:834 #, fuzzy msgid "" "The ‘run in background’ permission can be changed at any time from the " @@ -43,159 +43,249 @@ msgstr "" "Der Zugriff auf Ihre Lautsprecher kann in den Einstellungen zur Privatsphäre " "jederzeit geändert werden." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Mikrofon einschalten?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s möchte Ihre Kamera nutzen." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"Der Zugriff auf Ihr Mikrofon kann in den Einstellungen zur Privatsphäre " -"jederzeit geändert werden." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Eine Anwendung möchte auf Ihr Mikrofon zugreifen." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Eine Anwendung möchte Ihre Kamera nutzen." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s möchte auf Ihr Mikrofon zugreifen." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Lautsprecher einschalten?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Der Zugriff auf Ihre Lautsprecher kann in den Einstellungen zur Privatsphäre " -"jederzeit geändert werden." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Eine Anwendung möchte Ton wiedergeben." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s möchte Ton wiedergeben." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Kamera einschalten?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"Der Zugriff auf Ihre Kamera kann in den Einstellungen zur Privatsphäre " -"jederzeit geändert werden." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Eine Anwendung möchte Ihre Kamera nutzen." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "%s möchte Ihre Kamera nutzen." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "" -#: src/location.c:556 +#: src/location.c:571 #, fuzzy, c-format msgid "%s wants to use your location." msgstr "%s möchte Ihre Kamera nutzen." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "" -#: src/location.c:566 +#: src/location.c:581 #, fuzzy msgid "An application wants to use your location." msgstr "Eine Anwendung möchte Ihre Kamera nutzen." -#: src/location.c:569 +#: src/location.c:584 #, fuzzy msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Der Zugriff auf Ihre Lautsprecher kann in den Einstellungen zur Privatsphäre " "jederzeit geändert werden." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Eine Anwendung möchte Ton wiedergeben." -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Eine Anwendung möchte auf Ihr Mikrofon zugreifen." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 #, fuzzy msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Der Zugriff auf Ihre Lautsprecher kann in den Einstellungen zur Privatsphäre " "jederzeit geändert werden." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +msgid "Not allowed" +msgstr "" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "" -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 #, fuzzy msgid "Allow Applications to Set Backgrounds?" msgstr "Eine Anwendung möchte Ton wiedergeben." -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "" + +#~ msgid "Turn On Microphone?" +#~ msgstr "Mikrofon einschalten?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Der Zugriff auf Ihr Mikrofon kann in den Einstellungen zur Privatsphäre " +#~ "jederzeit geändert werden." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Eine Anwendung möchte auf Ihr Mikrofon zugreifen." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s möchte auf Ihr Mikrofon zugreifen." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Lautsprecher einschalten?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Der Zugriff auf Ihre Lautsprecher kann in den Einstellungen zur " +#~ "Privatsphäre jederzeit geändert werden." + +#~ msgid "An application wants to play sound." +#~ msgstr "Eine Anwendung möchte Ton wiedergeben." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s möchte Ton wiedergeben." + +#~ msgid "Turn On Camera?" +#~ msgstr "Kamera einschalten?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Der Zugriff auf Ihre Kamera kann in den Einstellungen zur Privatsphäre " +#~ "jederzeit geändert werden." diff --git a/po/en_GB.po b/po/en_GB.po index 16b04be..50ab8d0 100644 --- a/po/en_GB.po +++ b/po/en_GB.po @@ -2,38 +2,39 @@ # Copyright (C) 2019 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. # Zander Brown , 2019. +# Andi Chandler , 2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2019-08-24 23:48+0100\n" -"Last-Translator: Zander Brown \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-04-11 13:09+0100\n" +"Last-Translator: Andi Chandler \n" "Language-Team: English - United Kingdom \n" "Language: en_GB\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Gtranslator 3.32.1\n" +"X-Generator: Poedit 3.4.2\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Allow %s to run in the background?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s requests to be started automatically and run in the background." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s requests to run in the background." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,151 +42,252 @@ msgstr "" "The ‘run in background’ permission can be changed at any time from the " "application settings." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Don't allow" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Allow" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Turn On Microphone?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Allow %s to Use the Camera?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s wants to access camera devices." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Allow app to Use the Camera?" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "An application wants to use your microphone." +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "An app wants to access camera devices." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s wants to use your microphone." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Desktop file id missing .desktop suffix: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Turn On Speakers?" +#: src/dynamic-launcher.c:133 +#, fuzzy, c-format +msgid "Desktop file id is not valid" +msgstr "Desktop entry given to Install() not valid" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." -msgstr "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "Desktop entry given to Install() not a valid key file" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "An application wants to play sound." +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "Desktop entry given to Install() must have only one group" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s wants to play sound." +msgid "Token given is invalid: %s" +msgstr "Token given is invalid: %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Turn On Camera?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "Desktop entry given to Install() not valid" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Desktop file exceeds max size (%d): %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "An application wants to use your camera." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "URL given is invalid: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Invalid launcher type: %x" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s wants to use your camera." +msgid "Unsupported launcher type: %x" +msgstr "Unsupported launcher type: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Dynamic launcher icon failed validation" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() not allowed for app id %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Desktop file '%s' icon at unrecognised path" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Desktop file '%s' icon failed to serialise" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "No dynamic launcher exists with id '%s'" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "Failed to create GDesktopAppInfo for launcher with id '%s'" + +#: src/location.c:544 msgid "Deny Access" msgstr "Deny Access" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Grant Access" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Give %s Access to Your Location?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s wants to use your location." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Grant Access to Your Location?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "An application wants to use your location." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "Location access can be changed at any time from the privacy settings." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" -msgstr "" +msgstr "Deny" -#: src/screenshot.c:256 -#, fuzzy, c-format +#: src/screenshot.c:267 +#, c-format msgid "Allow %s to Take Screenshots?" -msgstr "Allow %s to run in the background?" +msgstr "Allow %s to Take Screenshots?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." -msgstr "" +msgstr "%s wants to be able to take screenshots at any time." -#: src/screenshot.c:265 -#, fuzzy +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" -msgstr "Allow %s to run in the background?" +msgstr "Allow Applications to Take Screenshots?" -#: src/screenshot.c:266 -#, fuzzy +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." -msgstr "An application wants to use your microphone." +msgstr "An application wants to be able to take screenshots at any time." -#: src/screenshot.c:269 src/wallpaper.c:218 -#, fuzzy +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." -msgstr "Location access can be changed at any time from the privacy settings." +msgstr "This permission can be changed at any time from the privacy settings." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Requested setting not found" -#: src/wallpaper.c:205 -#, fuzzy, c-format +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Don't allow" + +#: src/wallpaper.c:207 +#, c-format msgid "Allow %s to Set Backgrounds?" -msgstr "Allow %s to run in the background?" +msgstr "Allow %s to Set Backgrounds?" -#: src/wallpaper.c:206 -#, fuzzy, c-format +#: src/wallpaper.c:208 +#, c-format msgid "%s is requesting to be able to change the background image." -msgstr "%s requests to run in the background." +msgstr "%s is requesting to be able to change the background image." -#: src/wallpaper.c:215 -#, fuzzy +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" -msgstr "Allow %s to run in the background?" +msgstr "Allow Applications to Set Backgrounds?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "" +"An application is requesting to be able to change the background image." + +#, c-format +#~ msgid "Desktop file id missing app id prefix '%s.': %s" +#~ msgstr "Desktop file id missing app id prefix '%s.': %s" + +#, c-format +#~ msgid "Desktop entry given to Install() must not use --file-forwarding" +#~ msgstr "Desktop entry given to Install() must not use --file-forwarding" + +#, c-format +#~ msgid "DynamicLauncher install not supported for: %s" +#~ msgstr "DynamicLauncher install not supported for: %s" + +#~ msgid "Turn On Microphone?" +#~ msgstr "Turn On Microphone?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "An application wants to use your microphone." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s wants to use your microphone." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Turn On Speakers?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." + +#~ msgid "An application wants to play sound." +#~ msgstr "An application wants to play sound." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s wants to play sound." + +#~ msgid "Turn On Camera?" +#~ msgstr "Turn On Camera?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." diff --git a/po/es.po b/po/es.po index f948ce4..983964a 100644 --- a/po/es.po +++ b/po/es.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2021-12-04 14:52+0100\n" "Last-Translator: Adolfo Jayme Barrientos \n" "Language-Team: Spanish\n" @@ -17,22 +17,22 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "¿Quiere permitir que %s se ejecute en segundo plano?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s solicita iniciarse automáticamente y ejecutarse en segundo plano." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s solicita ejecutarse en segundo plano." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -40,155 +40,246 @@ msgstr "" "El permiso «ejecución en segundo plano» puede modificarse en cualquier " "momento a través de la configuración de privacidad." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "No permitir" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Permitir" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "¿Quiere encender el micrófono?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "¿Quiere permitir que %s se ejecute en segundo plano?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s intenta utilizar la cámara." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"El acceso al micrófono puede modificarse en cualquier momento a través de la " -"configuración de privacidad." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Una aplicación intenta utilizar el micrófono." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Una aplicación intenta utilizar la cámara." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s intenta utilizar el micrófono." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "¿Quiere encender los altavoces?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"El acceso a los altavoces puede modificarse en cualquier momento a través de " -"la configuración de privacidad." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Una aplicación intenta reproducir sonido." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s intenta reproducir sonido." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "¿Quiere encender la cámara?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"El acceso a la cámara puede modificarse en cualquier momento a través de la " -"configuración de privacidad." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Una aplicación intenta utilizar la cámara." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "%s intenta utilizar la cámara." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Denegar acceso" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Conceder acceso" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "¿Quiere conceder a %s acceso a su ubicación?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s intenta utilizar su ubicación." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "¿Quiere conceder acceso a su ubicación?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Una aplicación intenta utilizar su ubicación." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "El acceso a la ubicación puede modificarse en cualquier momento a través de " "la configuración de privacidad." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Denegar" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "¿Quiere permitir que %s establezca fondos?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "¿Quiere permitir que las aplicaciones establezcan fondos?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Una aplicación intenta utilizar el micrófono." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Este permiso puede modificarse en cualquier momento a través de la " "configuración de privacidad." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "No se encontró la configuración solicitada" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "No permitir" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "¿Quiere permitir que %s establezca fondos?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s solicita poder cambiar la imagen de fondo." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "¿Quiere permitir que las aplicaciones establezcan fondos?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Una aplicación solicita poder cambiar la imagen de fondo." + +#~ msgid "Turn On Microphone?" +#~ msgstr "¿Quiere encender el micrófono?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "El acceso al micrófono puede modificarse en cualquier momento a través de " +#~ "la configuración de privacidad." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Una aplicación intenta utilizar el micrófono." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s intenta utilizar el micrófono." + +#~ msgid "Turn On Speakers?" +#~ msgstr "¿Quiere encender los altavoces?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "El acceso a los altavoces puede modificarse en cualquier momento a través " +#~ "de la configuración de privacidad." + +#~ msgid "An application wants to play sound." +#~ msgstr "Una aplicación intenta reproducir sonido." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s intenta reproducir sonido." + +#~ msgid "Turn On Camera?" +#~ msgstr "¿Quiere encender la cámara?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "El acceso a la cámara puede modificarse en cualquier momento a través de " +#~ "la configuración de privacidad." diff --git a/po/fr.po b/po/fr.po index 4653bc7..c469c90 100644 --- a/po/fr.po +++ b/po/fr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2017-03-02 11:34+0100\n" "Last-Translator: Nicolas Cuffia \n" "Language-Team: French \n" @@ -17,22 +17,22 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "" -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "" -#: src/background.c:754 +#: src/background.c:834 #, fuzzy msgid "" "The ‘run in background’ permission can be changed at any time from the " @@ -41,159 +41,249 @@ msgstr "" "L'accès aux haut-parleurs peut être modifié n'importe quand à partir des " "paramètres de confidentialité." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Allumer le microphone ?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s veut utiliser votre caméra." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"L'accès au microphone peut être modifié n'importe quand à partir des " -"paramètres de confidentialité." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Une application veut utiliser votre microphone." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Une application veut utiliser votre caméra." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s veut utiliser votre microphone." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Allumer les haut-parleurs ?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"L'accès aux haut-parleurs peut être modifié n'importe quand à partir des " -"paramètres de confidentialité." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Une application veut jouer du son." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s veut jouer du son." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Allumer la caméra ?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"L'accès à la caméra peut être modifié n'importe quand à partir des " -"paramètres de confidentialité." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Une application veut utiliser votre caméra." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "%s veut utiliser votre caméra." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "" -#: src/location.c:556 +#: src/location.c:571 #, fuzzy, c-format msgid "%s wants to use your location." msgstr "%s veut utiliser votre caméra." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "" -#: src/location.c:566 +#: src/location.c:581 #, fuzzy msgid "An application wants to use your location." msgstr "Une application veut utiliser votre caméra." -#: src/location.c:569 +#: src/location.c:584 #, fuzzy msgid "Location access can be changed at any time from the privacy settings." msgstr "" "L'accès aux haut-parleurs peut être modifié n'importe quand à partir des " "paramètres de confidentialité." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Une application veut jouer du son." -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Une application veut utiliser votre microphone." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 #, fuzzy msgid "This permission can be changed at any time from the privacy settings." msgstr "" "L'accès aux haut-parleurs peut être modifié n'importe quand à partir des " "paramètres de confidentialité." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +msgid "Not allowed" +msgstr "" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "" -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 #, fuzzy msgid "Allow Applications to Set Backgrounds?" msgstr "Une application veut jouer du son." -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "" + +#~ msgid "Turn On Microphone?" +#~ msgstr "Allumer le microphone ?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L'accès au microphone peut être modifié n'importe quand à partir des " +#~ "paramètres de confidentialité." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Une application veut utiliser votre microphone." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s veut utiliser votre microphone." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Allumer les haut-parleurs ?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L'accès aux haut-parleurs peut être modifié n'importe quand à partir des " +#~ "paramètres de confidentialité." + +#~ msgid "An application wants to play sound." +#~ msgstr "Une application veut jouer du son." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s veut jouer du son." + +#~ msgid "Turn On Camera?" +#~ msgstr "Allumer la caméra ?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L'accès à la caméra peut être modifié n'importe quand à partir des " +#~ "paramètres de confidentialité." diff --git a/po/gl.po b/po/gl.po index 7851035..ccd1558 100644 --- a/po/gl.po +++ b/po/gl.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2023-02-08 21:13+0100\n" "Last-Translator: Fran Diéguez \n" "Language-Team: Galician\n" @@ -17,22 +17,22 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.2.2\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Permitir a %s executarse en segundo plano?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s solicita iniciarse automaticamente e en segundo plano." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s solicita executarse en segundo plano." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -40,155 +40,246 @@ msgstr "" "Pode cambiar o permiso «executarse en segundo plano» en calquera momento " "desde as preferencias da aplicación." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Non permitir" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Permitir" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Acender micrófono?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Permitir a %s executarse en segundo plano?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s quere uar a súa cámara." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"O acceso ao seu micrófono pode cambiarse en calquera momento desde as " -"preferencias de privacidade." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Unha aplicación quere usar o seu micrófono." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Unha aplicación quere usar a súa cámara." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s quere uar o seu micrófono." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Acender os altofalantes?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"O acceso aos seus altofalantes pode cambiarse en calquera momento desde as " -"preferencias de privacidade." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Unha aplicación quere reproducir son." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s querer reproducir son." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Acender a cámara?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"O acceso á súa cámara pode cambiarse en calquera momento desde as " -"preferencias de privacidade." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Unha aplicación quere usar a súa cámara." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "%s quere uar a súa cámara." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Denegar acceso" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Conceder acceso" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Desexa darlle acceso a %s á súa localización?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s quere usar a súa localización." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Desexa acceder acceso á súa localización?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Unha aplicación quere usar a súa localización." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "O acceso á súa localización pode cambiarse en calquera momento desde as " "preferencias de privacidade." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Denegar" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Permitirlle a %s configurar sacar capturas de pantalla?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s solicita poder sacar capturas de pantalla en calquer momento." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Permitirlle ás aplicacións sacar capturas de pantalla?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "" "Unha aplicación solicita poder sacar capturas de pantalla en calquera " "momento." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Este permiso pode cambiarse en calquera momento desde as preferencias de " "privacidade." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Non se atopou a configuración solicitada" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Non permitir" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Permitirlle a %s configurar o fondo de pantalla?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s está solicitando poder cambiar o fondo de pantalla." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Permitirlle ás aplicacións configurar o fondo de pantalla?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Unha aplicación está solicitando poder cambiar a imaxe de fondo." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Acender micrófono?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "O acceso ao seu micrófono pode cambiarse en calquera momento desde as " +#~ "preferencias de privacidade." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Unha aplicación quere usar o seu micrófono." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s quere uar o seu micrófono." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Acender os altofalantes?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "O acceso aos seus altofalantes pode cambiarse en calquera momento desde " +#~ "as preferencias de privacidade." + +#~ msgid "An application wants to play sound." +#~ msgstr "Unha aplicación quere reproducir son." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s querer reproducir son." + +#~ msgid "Turn On Camera?" +#~ msgstr "Acender a cámara?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "O acceso á súa cámara pode cambiarse en calquera momento desde as " +#~ "preferencias de privacidade." diff --git a/po/he.po b/po/he.po index aaea82f..b7f0562 100644 --- a/po/he.po +++ b/po/he.po @@ -1,14 +1,14 @@ # Hebrew translation for xdg-desktop-portal. # Copyright (C) 2022 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. -# Yosef Or Boczko , 2022. +# Yosef Or Boczko , 2022-2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal main\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2022-07-25 21:56+0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-09-26 13:18+0300\n" "Last-Translator: Yosef Or Boczko \n" "Language-Team: Hebrew \n" "Language: he\n" @@ -16,168 +16,256 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"X-Generator: Gtranslator 40.0\n" +"X-Generator: Gtranslator 46.1\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "לאפשר הרצה של %s ברקע?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "‏%s מבקש להיפתח באופן אוטומטי ולרוץ ברקע." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "‏%s מבקש לרוץ ברקע." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." msgstr "ניתן לשנות בכל עת את ההרשאה „לפעול ברקע” בהגדרות היישום." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "לא לאפשר" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "לאפשר" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "להפעיל את המיקרופון?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "לאפשר ליישום %s להשתמש במצלמה?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "ניתן לשנות בכל עת את הרשאות הגישה למיקרופון שלך בהגדרות הפרטיות." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "‏%s מבקש להשתמש בהתקני מצלמה." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "יישום מבקש להשתמש במיקרופון שלך." +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "לאפשר ליישום להשתמש במצלמה?" -#: src/device.c:123 +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "יישום מבקש להשתמש בהתקני מצלמה." + +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "‏%s מבקש להשתמש במיקרופון שלך." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "להפעיל את הרמקולים?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." -msgstr "ניתן לשנות בכל עת את הרשאות הגישה לרמקולים שלך בהגדרות הפרטיות." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "יישום מבקש להפעיל שמע." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "‏%s מבקש להפעיל שמע." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "להפעיל את המצלמה?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "ניתן לשנות בכל עת את הרשאות הגישה למצלמה שלך בהגדרות הפרטיות." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "יישום מבקש להשתמש במצלמה שלך." +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:982 #, c-format -msgid "%s wants to use your camera." -msgstr "‏%s מבקש להשתמש במצלמה שלך." +msgid "No dynamic launcher exists with id '%s'" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "מניעת גישה" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "הענקת גישה" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "לתת ליישום %s גישה למיקום שלך?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "‏%s מבקש להשתמש במיקום שלך." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "לאפשר גישה למיקום שלך?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "יישום מבקש להשתמש במיקום שלך." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "ניתן לשנות בכל עת את הרשאות הגישה למיקום שלך מהגדרות הפרטיות." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "דחייה" -#: src/screenshot.c:256 -#, fuzzy, c-format +#: src/screenshot.c:267 +#, c-format msgid "Allow %s to Take Screenshots?" -msgstr "לאפשר ליישום %s לקבוע את הרקעים?" +msgstr "לאפשר ליישום %s לצלם את המסך?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." -msgstr "" +msgstr "‏%s רוצה הרשאה לצלם את המסך בכל רגע נתון." -#: src/screenshot.c:265 -#, fuzzy +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" -msgstr "לאפשר ליישומים לקבוע את הרקעים?" +msgstr "לאפשר ליישומים לצלם את המסך?" -#: src/screenshot.c:266 -#, fuzzy +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." -msgstr "יישום מבקש להשתמש במיקרופון שלך." +msgstr "יישום מבקש הרשאה לצלם את המסך בכל רגע נתון." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "ניתן לשנות בכל עת הרשאה זו בהגדרות הפרטיות." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "ההגדרה המבוקשת לא נמצאה" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "לא לאפשר" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "לאפשר ליישום %s לקבוע את הרקעים?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "‏%s מבקש הרשאה לשינוי תמונת הרקע." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "לאפשר ליישומים לקבוע את הרקעים?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "יישום מבקש הרשאה לשינוי תמונת הרקע." + +#~ msgid "Turn On Microphone?" +#~ msgstr "להפעיל את המיקרופון?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "ניתן לשנות בכל עת את הרשאות הגישה למיקרופון שלך בהגדרות הפרטיות." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "יישום מבקש להשתמש במיקרופון שלך." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "‏%s מבקש להשתמש במיקרופון שלך." + +#~ msgid "Turn On Speakers?" +#~ msgstr "להפעיל את הרמקולים?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "ניתן לשנות בכל עת את הרשאות הגישה לרמקולים שלך בהגדרות הפרטיות." + +#~ msgid "An application wants to play sound." +#~ msgstr "יישום מבקש להפעיל שמע." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "‏%s מבקש להפעיל שמע." + +#~ msgid "Turn On Camera?" +#~ msgstr "להפעיל את המצלמה?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "ניתן לשנות בכל עת את הרשאות הגישה למצלמה שלך בהגדרות הפרטיות." diff --git a/po/hi.po b/po/hi.po index 076b171..2b7c532 100644 --- a/po/hi.po +++ b/po/hi.po @@ -2,182 +2,247 @@ # Copyright (C) 2021 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. # Dmitry , 2021. +# Scrambled777 , 2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal 1.8.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2021-09-09 00:42+0700\n" -"Last-Translator: Dmitry \n" -"Language-Team: Hindi \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-05-18 09:47+0530\n" +"Last-Translator: Scrambled777 \n" +"Language-Team: Hindi \n" "Language: hi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.2.4\n" -"Plural-Forms: nplurals=2; plural=(n==0 || n==1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Gtranslator 46.1\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" -msgstr "अनुमति देना %s पृष्ठभूमि में चलाने के लिए?" +msgstr "%s को पृष्ठभूमि में चलने की अनुमति दें?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." -msgstr "%s अनुरोध स्वचालित रूप से शुरू होने और पृष्ठभूमि में चलाने का अनुरोध करता है।" +msgstr "%s स्वचालित रूप से प्रारंभ करने और पृष्ठभूमि में चलाने का अनुरोध कर रहा है।" -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." -msgstr "%s पृष्ठभूमि में चलाने के लिए अनुरोध।" +msgstr "%s पृष्ठभूमि में चलने का अनुरोध कर रहा है।" -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." -msgstr "एप्लिकेशन सेटिंग से किसी भी समय 'रन इन बैकग्राउंड' अनुमति को बदला जा सकता है।" +msgstr "‘पृष्ठभूमि में चलाएं’ अनुमति को अनुप्रयोग सेटिंग्स से किसी भी समय बदला जा सकता है।" -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "अनुमति न दें" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" -msgstr "अनुमति" +msgstr "अनुमति दें" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "माइक्रोफ़ोन चालू करें?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "%s को कैमरा उपयोग करने की अनुमति दें?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "गोपनीयता सेटिंग्स से आपके माइक्रोफ़ोन तक पहुंच को किसी भी समय बदला जा सकता है।" +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s कैमरा उपकरणों तक पहुंचना चाहता है।" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "एक एप्लिकेशन आपके माइक्रोफ़ोन का उपयोग करना चाहता है।" +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "ऐप को कैमरे का उपयोग करने की अनुमति दें?" -#: src/device.c:123 +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "ऐप कैमरा उपकरण तक पहुंचना चाहता है।" + +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s आपके माइक्रोफ़ोन का उपयोग करना चाहता है।" +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "डेस्कटॉप फाइल ID में .desktop प्रत्यय मौजूद नहीं है: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "स्पीकर चालू करें?" +#: src/dynamic-launcher.c:133 +#, fuzzy, c-format +msgid "Desktop file id is not valid" +msgstr "Install() को दी गई डेस्कटॉप प्रविष्टि मान्य नहीं है" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." -msgstr "गोपनीयता सेटिंग्स से आपके स्पीकर तक पहुंच को किसी भी समय बदला जा सकता है।" +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "Install() को दी गई डेस्कटॉप प्रविष्टि मान्य कुंजी फाइल नहीं है" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "एक एप्लिकेशन ध्वनि बजाना चाहता है।" +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "Install() को दी गई डेस्कटॉप प्रविष्टि में केवल एक समूह होना चाहिए" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s ध्वनि बजाना चाहता है।" +msgid "Token given is invalid: %s" +msgstr "दिया गया टोकन अमान्य है: %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "कैमरा चालू करें?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "Install() को दी गई डेस्कटॉप प्रविष्टि मान्य नहीं है" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "गोपनीयता सेटिंग्स से आपके कैमरे तक पहुंच को किसी भी समय बदला जा सकता है।" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "डेस्कटॉप फाइल अधिकतम आकार (%d) से अधिक है: %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "एक एप्लिकेशन आपके कैमरे का उपयोग करना चाहता है।" +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "दिया गया URL अमान्य है: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "अमान्य लॉन्चर प्रकार: %x" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "असमर्थित लॉन्चर प्रकार: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "डायनैमिक लॉन्चर आइकन का सत्यापन विफल रहा" -#: src/device.c:149 +#: src/dynamic-launcher.c:741 #, c-format -msgid "%s wants to use your camera." -msgstr "%s अपने कैमरे का उपयोग करना चाहता है।" +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "ऐप ID %s के लिए RequestInstallToken() की अनुमति नहीं है" -#: src/location.c:527 +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "अज्ञात पथ पर डेस्कटॉप फाइल '%s' आइकन" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "डेस्कटॉप फाइल '%s' आइकन क्रमबद्ध करने में विफल रहा" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "ID '%s' के साथ कोई डायनैमिक लॉन्चर मौजूद नहीं है" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "'%s' ID वाले लॉन्चर के लिए GDesktopAppInfo बनाने में विफल" + +#: src/location.c:544 msgid "Deny Access" -msgstr "पहुँच को अस्वीकार" +msgstr "पहुंच अस्वीकारें" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" -msgstr "अनुदान पहुँच" +msgstr "पहुंच स्वीकारें" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" -msgstr "देना %s आपके स्थान तक पहुँच?" +msgstr "%s को अपने स्थान तक पहुंच प्रदान करें?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s आपके स्थान का उपयोग करना चाहता है।" -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "अपने स्थान तक पहुंच प्रदान करें?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." -msgstr "एक एप्लिकेशन आपके स्थान का उपयोग करना चाहता है।" +msgstr "एक अनुप्रयोग आपके स्थान का उपयोग करना चाहता है।" -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." -msgstr "गोपनीयता सेटिंग्स से किसी भी समय स्थान का उपयोग बदला जा सकता है।" +msgstr "गोपनीयता सेटिंग्स से किसी भी समय स्थान तक पहुंच को बदला जा सकता है।" -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" -msgstr "मना" +msgstr "अस्वीकारें" -#: src/screenshot.c:256 -#, fuzzy, c-format +#: src/screenshot.c:267 +#, c-format msgid "Allow %s to Take Screenshots?" -msgstr "परमिट %s पृष्ठभूमि सेट करने के लिए?" +msgstr "%s को स्क्रीनशॉट लेने की अनुमति दें?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." -msgstr "" +msgstr "%s किसी भी समय स्क्रीनशॉट लेने में सक्षम होना चाहता है।" -#: src/screenshot.c:265 -#, fuzzy +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" -msgstr "एप्‍लिकेशन को पृष्‍ठभूमि सेट करने की अनुमति दें?" +msgstr "अनुप्रयोग को स्क्रीनशॉट लेने की अनुमति दें?" -#: src/screenshot.c:266 -#, fuzzy +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." -msgstr "एक एप्लिकेशन आपके माइक्रोफ़ोन का उपयोग करना चाहता है।" +msgstr "एक अनुप्रयोग किसी भी समय स्क्रीनशॉट लेने में सक्षम होना चाहता है।" -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "इस अनुमति को गोपनीयता सेटिंग्स से किसी भी समय बदला जा सकता है।" -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "अनुरोधित सेटिंग नहीं मिली" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "अनुमति न दें" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" -msgstr "परमिट %s पृष्ठभूमि सेट करने के लिए?" +msgstr "%s को पृष्ठभूमि निर्धारित करने की अनुमति दें?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s पृष्ठभूमि छवि को बदलने में सक्षम होने का अनुरोध कर रहा है।" -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" -msgstr "एप्‍लिकेशन को पृष्‍ठभूमि सेट करने की अनुमति दें?" +msgstr "अनुप्रयोग को पृष्ठभूमि निर्धारित करने की अनुमति दें?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." -msgstr "एक एप्लिकेशन पृष्ठभूमि छवि को बदलने में सक्षम होने का अनुरोध कर रहा है।" +msgstr "एक अनुप्रयोग पृष्ठभूमि छवि को बदलने में सक्षम होने का अनुरोध कर रहा है।" + +#, c-format +#~ msgid "Desktop file id missing app id prefix '%s.': %s" +#~ msgstr "डेस्कटॉप फाइल ID में ऐप ID उपसर्ग '%s.' नहीं है: %s" + +#, c-format +#~ msgid "Desktop entry given to Install() must not use --file-forwarding" +#~ msgstr "" +#~ "Install() को दी गई डेस्कटॉप प्रविष्टि को --file-forwarding का उपयोग नहीं करना " +#~ "चाहिए" + +#, c-format +#~ msgid "DynamicLauncher install not supported for: %s" +#~ msgstr "डायनैमिक लॉन्चर स्थापना इसके लिए समर्थित नहीं है: %s" diff --git a/po/hr.po b/po/hr.po index e8c7995..aa13014 100644 --- a/po/hr.po +++ b/po/hr.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2020-01-25 16:44+0100\n" "Last-Translator: Milo Ivir \n" "Language-Team: \n" @@ -18,22 +18,22 @@ msgstr "" "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 1.8.12\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Dozvoliti da %s radi u pozadini?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s traži da se automatski pokrene i da radi u pozadini." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s traži da radi u pozadini." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,150 +41,243 @@ msgstr "" "Dozvolu za „radi u pozadini” je uvijek moguće promijeniti u postavkama " "programa." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Nemoj dozvoliti" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Dozvoli" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Uključiti mikrofon?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Dozvoliti da %s radi u pozadini?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s želi koristiti tvoju kameru." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"Pristup tvom mikrofonu je uvijek moguće promijeniti u postavkama privatnosti." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Jedan program želi koristiti tvoj mikrofon." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Jedan program želi koristiti tvoju kameru." + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:123 +#: src/dynamic-launcher.c:133 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s želi koristiti tvoj mikrofon." +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Uključiti zvučnike?" +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" msgstr "" -"Pristup tvojim zvučnicima je uvijek moguće promijeniti u postavkama " -"privatnosti." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Jedan program želi svirati zvuk." +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:400 #, c-format -msgid "%s wants to play sound." -msgstr "%s želi svirati zvuk." +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Uključiti kameru?" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" msgstr "" -"Pristup tvojoj kameri je uvijek moguće promijeniti u postavkama privatnosti." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Jedan program želi koristiti tvoju kameru." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s želi koristiti tvoju kameru." +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Zabrani pristup" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Dozvoli pristup" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Dozvoliti da %s pristupi tvojoj lokaciji?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s želi koristiti tvoju lokaciju." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Dozvoliti pristup tvojoj lokaciji?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Jedan program želi koristiti tvoju lokaciju." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Pristup lokaciji je uvijek moguće promijeniti u postavkama privatnosti." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Zabrani" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "Dozvoliti da %s postavi pozadine?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Dozvoliti programu da postavi pozadine?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Jedan program želi koristiti tvoj mikrofon." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "Ovu dozvolu je uvijek moguće promijeniti u postavkama privatnosti." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Zatražena postavka nije nađena" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Nemoj dozvoliti" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Dozvoliti da %s postavi pozadine?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s traži dozvolu za mijenjanje slike pozadine." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Dozvoliti programu da postavi pozadine?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Jedan program traži dozvolu za mijenjanje slike pozadine." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Uključiti mikrofon?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Pristup tvom mikrofonu je uvijek moguće promijeniti u postavkama " +#~ "privatnosti." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Jedan program želi koristiti tvoj mikrofon." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s želi koristiti tvoj mikrofon." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Uključiti zvučnike?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Pristup tvojim zvučnicima je uvijek moguće promijeniti u postavkama " +#~ "privatnosti." + +#~ msgid "An application wants to play sound." +#~ msgstr "Jedan program želi svirati zvuk." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s želi svirati zvuk." + +#~ msgid "Turn On Camera?" +#~ msgstr "Uključiti kameru?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Pristup tvojoj kameri je uvijek moguće promijeniti u postavkama " +#~ "privatnosti." diff --git a/po/hu.po b/po/hu.po index 0bd6f82..761860c 100644 --- a/po/hu.po +++ b/po/hu.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2022-09-21 22:58+0200\n" "Last-Translator: Balázs Úr \n" "Language-Team: Hungarian \n" @@ -18,22 +18,22 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 19.12.3\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Engedélyezi a(z) %s számára, hogy a háttérben fusson?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "A(z) %s azt kéri, hogy automatikusan elinduljon és a háttérben fusson." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "A(z) %s azt kéri, hogy a háttérben fusson." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,153 +41,244 @@ msgstr "" "A „háttérben való futás” engedély bármikor megváltoztatható az alkalmazás " "beállításaiban." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Ne engedélyezze" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Engedélyezés" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Mikrofon bekapcsolása?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Engedélyezi a(z) %s számára, hogy a háttérben fusson?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "A(z) %s használni szeretné a kameráját." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"A mikrofonhoz való hozzáférés bármikor megváltoztatható az adatvédelmi " -"beállításokban." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Egy alkalmazás használni szeretné a mikrofonját." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Egy alkalmazás használni szeretné a kameráját." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "A(z) %s használni szeretné a mikrofonját." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Hangszórók bekapcsolása?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"A hangszórókhoz való hozzáférés bármikor megváltoztatható az adatvédelmi " -"beállításokban." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Egy alkalmazás hangot szeretne lejátszani." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "A(z) %s hangot szeretne lejátszani." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Kamera bekapcsolása?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"A kamerához való hozzáférés bármikor megváltoztatható az adatvédelmi " -"beállításokban." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Egy alkalmazás használni szeretné a kameráját." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "A(z) %s használni szeretné a kameráját." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Hozzáférés megtagadása" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Hozzáférés megadása" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Hozzáférést ad a(z) %s számára a tartózkodási helyéhez?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "A(z) %s használni szeretné a tartózkodási helyét." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Hozzáférést ad a tartózkodási helyéhez?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Egy alkalmazás használni szeretné a tartózkodási helyét." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "A tartózkodási helyhez való hozzáférés bármikor megváltoztatható az " "adatvédelmi beállításokban." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Elutasítás" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Engedélyezi a(z) %s számára, hogy képernyőképeket készítsen?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "A(z) %s azt szeretné, hogy bármikor készíthessen képernyőképeket." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Engedélyezi az alkalmazások számára, hogy képernyőképeket készítsenek?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "" "Egy alkalmazás azt szeretné, hogy bármikor készíthessen képernyőképeket." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Ez az engedély bármikor megváltoztatható az adatvédelmi beállításokban." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "A kért beállítás nem található" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Ne engedélyezze" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Engedélyezi a(z) %s számára, hogy háttereket állítson be?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "A(z) %s azt kéri, hogy megváltoztathassa a háttérképet." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Engedélyezi az alkalmazások számára, hogy háttereket állítsanak be?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Egy alkalmazás azt kéri, hogy megváltoztathassa a háttérképet." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Mikrofon bekapcsolása?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "A mikrofonhoz való hozzáférés bármikor megváltoztatható az adatvédelmi " +#~ "beállításokban." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Egy alkalmazás használni szeretné a mikrofonját." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "A(z) %s használni szeretné a mikrofonját." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Hangszórók bekapcsolása?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "A hangszórókhoz való hozzáférés bármikor megváltoztatható az adatvédelmi " +#~ "beállításokban." + +#~ msgid "An application wants to play sound." +#~ msgstr "Egy alkalmazás hangot szeretne lejátszani." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "A(z) %s hangot szeretne lejátszani." + +#~ msgid "Turn On Camera?" +#~ msgstr "Kamera bekapcsolása?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "A kamerához való hozzáférés bármikor megváltoztatható az adatvédelmi " +#~ "beállításokban." diff --git a/po/id.po b/po/id.po index aadeda2..a33e854 100644 --- a/po/id.po +++ b/po/id.po @@ -2,38 +2,39 @@ # Copyright (C) 2017 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. # Kukuh Syafaat , 2017-2020, 2022. +# Andika Triwidada , 2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2022-11-30 10:52+0700\n" -"Last-Translator: Kukuh Syafaat \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-10-31 20:42+0700\n" +"Last-Translator: Andika Triwidada \n" "Language-Team: Indonesian \n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.2.1\n" +"X-Generator: Poedit 3.5\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Izinkan %s berjalan di latar belakang?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "" "%s meminta untuk dimulai secara otomatis dan dijalankan di latar belakang." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s meminta untuk berjalan di latar belakang." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,146 +42,197 @@ msgstr "" "Izin 'berjalan di latar belakang' dapat diubah kapan saja dari pengaturan " "aplikasi." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Jangan izinkan" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Izinkan" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Nyalakan Mikrofon?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Izinkan %s Menggunakan Kamera?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"Akses ke mikrofon Anda dapat diubah sewaktu-waktu dari pengaturan privasi." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s ingin mengakses perangkat kamera." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Izinkan aplikasi Menggunakan Kamera?" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Aplikasi ingin menggunakan mikrofon Anda." +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Sebuah aplikasi ingin mengakses perangkat kamera." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s ingin menggunakan mikrofon Anda." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Id berkas desktop kurang akhiran .desktop: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Nyalakan Speaker?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "Id berkas desktop tidak valid" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Akses ke speaker Anda dapat berubah sewaktu-waktu dari pengaturan privasi." +"Entri desktop yang diberikan ke Install() bukan berkas kunci yang valid" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Aplikasi ingin memutar suara." +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" +"Entri desktop yang diberikan ke Install() hanya boleh memiliki satu grup" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s ingin memutar suara." +msgid "Token given is invalid: %s" +msgstr "Token yang diberikan tidak valid: %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Nyalakan Kamera?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "Entri desktop yang diberikan ke Install() tidak valid" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "" -"Akses ke kamera Anda dapat diubah sewaktu-waktu dari pengaturan privasi." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Berkas desktop melebihi ukuran maks. (%d): %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Aplikasi ingin menggunakan kamera Anda." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "URL yang diberikan tidak valid: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Jenis peluncur tidak valid: %x" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s ingin menggunakan kamera Anda." +msgid "Unsupported launcher type: %x" +msgstr "Jenis peluncur yang tidak didukung: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Ikon peluncur dinamis gagal divalidasi" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() tidak diizinkan untuk id aplikasi %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Ikon \"%s\" berkas desktop di path yang tidak dikenali" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Ikon \"%s\" berkas desktop gagal diserialisasi" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Tidak ada peluncur dinamis dengan id '%s'" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "Gagal membuat GDesktopAppInfo untuk peluncur dengan id '%s'" + +#: src/location.c:544 msgid "Deny Access" msgstr "Tolak Akses" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Beri Akses" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Beri Akses %s ke Lokasi Anda?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s ingin menggunakan lokasi Anda." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Beri Akses ke Lokasi Anda?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Sebuah aplikasi ingin menggunakan lokasi Anda." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "Akses lokasi dapat diubah setiap saat dari pengaturan privasi." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Tolak" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Izinkan %s Mengambil Cuplikan Layar?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s ingin dapat mengambil cuplikan layar kapan saja." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Izinkan Aplikasi Mengambil Cuplikan Layar?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Sebuah aplikasi ingin dapat mengambil cuplikan layar kapan saja." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "Izin ini dapat diubah kapan saja dari pengaturan privasi." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Pengaturan yang diminta tidak ditemukan" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Jangan izinkan" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Izinkan %s untuk Mengatur Latar Belakang?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s meminta untuk dapat mengubah gambar latar belakang." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Izinkan Aplikasi untuk Mengatur Latar Belakang?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Aplikasi meminta untuk dapat mengubah gambar latar belakang." diff --git a/po/ie.po b/po/ie.po new file mode 100644 index 0000000..40fc9a6 --- /dev/null +++ b/po/ie.po @@ -0,0 +1,239 @@ +# Interlingue translation for xdg-desktop-portal. +# Copyright (C) 2024 xdg-desktop-portal's COPYRIGHT HOLDER +# This file is distributed under the same license as the xdg-desktop-portal package. +# Olga Smirnova , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: xdg-desktop-portal main\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-08-18 07:46+0700\n" +"Last-Translator: OIS \n" +"Language-Team: Interlingue\n" +"Language: ie\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Gtranslator 46.1\n" + +#: src/background.c:827 +#, c-format +msgid "Allow %s to run in the background?" +msgstr "Permisser a %s executer in li funde?" + +#: src/background.c:831 +#, c-format +msgid "%s requests to be started automatically and run in the background." +msgstr "%s desira esser lansat automaticmen e executer in li funde." + +#: src/background.c:833 +#, c-format +msgid "%s requests to run in the background." +msgstr "%s desira executer in li funde." + +#: src/background.c:834 +msgid "" +"The ‘run in background’ permission can be changed at any time from the " +"application settings." +msgstr "" +"Li permission a executer in li funde posse esser alterat in chascun moment " +"in li parametres del application." + +#: src/background.c:838 +msgid "Don't allow" +msgstr "Ne permisser" + +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 +msgid "Allow" +msgstr "Permisser" + +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Permisser a %s usar li cámera?" + +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s desira have accesse a vor cámeras." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Permisser al application usar li cámera?" + +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Un application desira have accesse a vor cámeras." + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" + +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" + +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" + +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" + +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" + +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" + +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 +msgid "Deny Access" +msgstr "Refusar li accesse" + +#: src/location.c:546 +msgid "Grant Access" +msgstr "Permisser li accesse" + +#: src/location.c:566 +#, c-format +msgid "Give %s Access to Your Location?" +msgstr "Far %s saver vor localisation?" + +#: src/location.c:571 +#, c-format +msgid "%s wants to use your location." +msgstr "%s desira saver vor localisation." + +#: src/location.c:580 +msgid "Grant Access to Your Location?" +msgstr "Far it saver vor localisation?" + +#: src/location.c:581 +msgid "An application wants to use your location." +msgstr "Un application desira usar vor localisation." + +#: src/location.c:584 +msgid "Location access can be changed at any time from the privacy settings." +msgstr "" +"Li accesse al localisation posse esser alterat in chascun moment in li " +"parametres de privatie." + +#: src/screenshot.c:245 src/wallpaper.c:185 +msgid "Deny" +msgstr "Refusar" + +#: src/screenshot.c:267 +#, c-format +msgid "Allow %s to Take Screenshots?" +msgstr "Permisser a %s capturar li ecran?" + +#: src/screenshot.c:268 +#, c-format +msgid "%s wants to be able to take screenshots at any time." +msgstr "%s desira li habilitá capturar vor ecran in alquel moment." + +#: src/screenshot.c:276 +msgid "Allow Applications to Take Screenshots?" +msgstr "Permisser a applicationes capturar li ecran?" + +#: src/screenshot.c:277 +msgid "An application wants to be able to take screenshots at any time." +msgstr "Un application desira li habilitá capturar vor ecran in alquel moment." + +#: src/screenshot.c:280 src/wallpaper.c:220 +msgid "This permission can be changed at any time from the privacy settings." +msgstr "" +"Ti permission posse esser alterat in chascun moment in li parametres de " +"privatie." + +#: src/settings.c:187 src/settings.c:223 +msgid "Requested setting not found" +msgstr "" + +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Ne permisser" + +#: src/wallpaper.c:207 +#, c-format +msgid "Allow %s to Set Backgrounds?" +msgstr "Permisser a %s cambiar tapetes?" + +#: src/wallpaper.c:208 +#, c-format +msgid "%s is requesting to be able to change the background image." +msgstr "%s desira li habilitá cambiar vor tapete.." + +#: src/wallpaper.c:217 +msgid "Allow Applications to Set Backgrounds?" +msgstr "Permisser a applicationes cambiar tapetes?" + +#: src/wallpaper.c:218 +msgid "An application is requesting to be able to change the background image." +msgstr "Un application desira li habilitá cambiar vor tapete." diff --git a/po/it.po b/po/it.po index 867f925..ecad135 100644 --- a/po/it.po +++ b/po/it.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2020-02-10 16:11+0100\n" "Last-Translator: Milo Casagrande \n" "Language-Team: Italian \n" @@ -18,24 +18,24 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 2.2.4\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Consentire a %s di andare in esecuzione sullo sfondo?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "" "L'applicazione %s richiede di essere avviata automaticamente e di essere " "eseguita sullo sfondo." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "L'applicazione %s richiede di essere eseguita sullo sfondo." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -43,155 +43,246 @@ msgstr "" "Il permesso «esegui sullo sfondo» può essere modificato in qualsiasi momento " "attraverso le impostazioni dell'applicazione." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Non consentire" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Consenti" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Accendere il microfono?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Consentire a %s di andare in esecuzione sullo sfondo?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s vuole utilizzare la videocamera." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"L'accesso al microfono può essere modificato in qualsiasi momento attraverso " -"le impostazioni della privacy." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Un'applicazione vuole utilizzare il microfono." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Un'applicazione vuole utilizzare la videocamera." + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:123 +#: src/dynamic-launcher.c:133 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s vuole utilizzare il microfono." +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Accendere gli altoparlanti?" +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" msgstr "" -"L'accesso al microfono può essere modificato in qualsiasi momento attraverso " -"le impostazioni della privacy." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Un'applicazione vuole riprodurre un suono." +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:400 #, c-format -msgid "%s wants to play sound." -msgstr "%s vuole riprodurre un suono." +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Accendere la videocamera?" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" msgstr "" -"L'accesso alla videocamera può essere modificato in qualsiasi momento " -"attraverso le impostazioni della privacy." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Un'applicazione vuole utilizzare la videocamera." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s vuole utilizzare la videocamera." +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Nega accesso" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Consenti accesso" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Consentire a %s di accedere alla propria posizione?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s vuole utilizzare la propria posizione." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Consentire l'accesso alla propria posizione?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Un'applicazione vuole utilizzare la propria posizione." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "L'accesso alla posizione può essere modificato in qualsiasi momento " "attraverso le impostazioni della privacy." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Nega" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "Consentire a %s di impostare lo sfondo?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Consentire alle applicazioni di impostare lo sfondo?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Un'applicazione vuole utilizzare il microfono." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Questa impostazione può essere modificata in qualsiasi momento attraverso le " "impostazioni della privacy." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "La risorsa richiesta non è stata trovata" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Non consentire" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Consentire a %s di impostare lo sfondo?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "L'applicazione %s richiede di poter cambiare l'immagine di sfondo." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Consentire alle applicazioni di impostare lo sfondo?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Un'applicazione richiede di poter cambiare l'immagine di sfondo." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Accendere il microfono?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L'accesso al microfono può essere modificato in qualsiasi momento " +#~ "attraverso le impostazioni della privacy." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Un'applicazione vuole utilizzare il microfono." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s vuole utilizzare il microfono." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Accendere gli altoparlanti?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L'accesso al microfono può essere modificato in qualsiasi momento " +#~ "attraverso le impostazioni della privacy." + +#~ msgid "An application wants to play sound." +#~ msgstr "Un'applicazione vuole riprodurre un suono." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s vuole riprodurre un suono." + +#~ msgid "Turn On Camera?" +#~ msgstr "Accendere la videocamera?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L'accesso alla videocamera può essere modificato in qualsiasi momento " +#~ "attraverso le impostazioni della privacy." diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 0000000..6c75aa3 --- /dev/null +++ b/po/ja.po @@ -0,0 +1,235 @@ +# Japanese translations for xdg-desktop-portal package. +# Copyright (C) 2024 THE xdg-desktop-portal'S COPYRIGHT HOLDER +# This file is distributed under the same license as the xdg-desktop-portal package. +# Ryo Nakano , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: xdg-desktop-portal\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-06-08 19:16+0900\n" +"Last-Translator: Ryo Nakano \n" +"Language-Team: none\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: src/background.c:827 +#, c-format +msgid "Allow %s to run in the background?" +msgstr "%s にバックグラウンドでの実行を許可しますか?" + +#: src/background.c:831 +#, c-format +msgid "%s requests to be started automatically and run in the background." +msgstr "%s が自動起動とバックグラウンドでの実行を要求しています。" + +#: src/background.c:833 +#, c-format +msgid "%s requests to run in the background." +msgstr "%s がバックグラウンドでの実行を要求しています。" + +#: src/background.c:834 +msgid "" +"The ‘run in background’ permission can be changed at any time from the " +"application settings." +msgstr "" +"‘バックグラウンドでの実行’ の権限は、アプリケーションの設定からいつでも変更可" +"能です。" + +#: src/background.c:838 +msgid "Don't allow" +msgstr "許可しない" + +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 +msgid "Allow" +msgstr "許可" + +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "%s にカメラの使用を許可しますか?" + +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s がカメラデバイスへのアクセスを求めています。" + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "アプリにカメラの使用を許可しますか?" + +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "アプリがカメラデバイスへのアクセスを求めています。" + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" + +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" + +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" + +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" + +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" + +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" + +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 +msgid "Deny Access" +msgstr "アクセスを拒否" + +#: src/location.c:546 +msgid "Grant Access" +msgstr "アクセスを許可" + +#: src/location.c:566 +#, c-format +msgid "Give %s Access to Your Location?" +msgstr "%s に位置情報へのアクセスを許可しますか?" + +#: src/location.c:571 +#, c-format +msgid "%s wants to use your location." +msgstr "%s が位置情報の使用を求めています。" + +#: src/location.c:580 +msgid "Grant Access to Your Location?" +msgstr "位置情報へのアクセスを許可しますか?" + +#: src/location.c:581 +msgid "An application wants to use your location." +msgstr "アプリケーションが位置情報の使用を求めています。" + +#: src/location.c:584 +msgid "Location access can be changed at any time from the privacy settings." +msgstr "位置情報へのアクセスは、プライバシーの設定からいつでも変更可能です。" + +#: src/screenshot.c:245 src/wallpaper.c:185 +msgid "Deny" +msgstr "拒否" + +#: src/screenshot.c:267 +#, c-format +msgid "Allow %s to Take Screenshots?" +msgstr "%s にスクリーンショットの撮影を許可しますか?" + +#: src/screenshot.c:268 +#, c-format +msgid "%s wants to be able to take screenshots at any time." +msgstr "%s が随時スクリーンショットを撮影できる権限を求めています。" + +#: src/screenshot.c:276 +msgid "Allow Applications to Take Screenshots?" +msgstr "アプリケーションにスクリーンショットの撮影を許可しますか?" + +#: src/screenshot.c:277 +msgid "An application wants to be able to take screenshots at any time." +msgstr "" +"アプリケーションが随時スクリーンショットを撮影できる権限を求めています。" + +#: src/screenshot.c:280 src/wallpaper.c:220 +msgid "This permission can be changed at any time from the privacy settings." +msgstr "この権限は、プライバシーの設定からいつでも変更可能です。" + +#: src/settings.c:187 src/settings.c:223 +msgid "Requested setting not found" +msgstr "要求された設定が見つかりませんでした" + +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "許可しない" + +#: src/wallpaper.c:207 +#, c-format +msgid "Allow %s to Set Backgrounds?" +msgstr "%s に背景の設定を許可しますか?" + +#: src/wallpaper.c:208 +#, c-format +msgid "%s is requesting to be able to change the background image." +msgstr "%s が背景画像を変更できる権限を求めています。" + +#: src/wallpaper.c:217 +msgid "Allow Applications to Set Backgrounds?" +msgstr "アプリケーションに背景の設定を許可しますか?" + +#: src/wallpaper.c:218 +msgid "An application is requesting to be able to change the background image." +msgstr "アプリケーションが背景画像を変更できる権限を求めています。" diff --git a/po/ka.po b/po/ka.po index 8074b0f..6af9fdf 100644 --- a/po/ka.po +++ b/po/ka.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2023-02-25 14:53+0100\n" "Last-Translator: Temuri Doghonadze \n" "Language-Team: Georgian <(nothing)>\n" @@ -18,22 +18,22 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.2.2\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "დავუშვა %s-ის ფონურ რეჟიმში გაშვება?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s ავტომატურად გაშვებას და ფონურ რეჟიმს ითხოვს." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s ითხოვს გაშვებას ფონზე." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,153 +41,244 @@ msgstr "" "„ფონში გაშვების“ ნებართვა ნებისმიერ დროს აპლიკაციის პარამეტრებიდან შეიძლება " "შეიცვალოს." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "არ დაშვება" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "დაშვება" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "ჩავრთო მიკროფონი?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "დავუშვა %s-ის ფონურ რეჟიმში გაშვება?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s-ს სურს გამოიყენოს თქვენი კამერა." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"თქვენს მიკროფონზე წვდომა ნებისმიერ დროს კონფიდენციალურობის პარამეტრებიდან " -"შეიძლება შეიცვალოს." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "აპლიკაციას სურს გამოიყენოს თქვენი მიკროფონი." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "აპლიკაციას სურს გამოიყენოს თქვენი კამერა." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s-ს სურს თქვენი მიკროფონი გამოიყენოს." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "ჩავრთო დინამიკები?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"თქვენს დინამიკებზე წვდომა შეიძლება შეიცვალოს ნებისმიერ დროს " -"კონფიდენციალურობის პარამეტრებიდან." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "აპლიკაციას ხმის გამოცემა სურს." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" + +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:400 #, c-format -msgid "%s wants to play sound." -msgstr "%s-ს ხმის გამოცემა სურს." +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "ჩავრთო კამერა?" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" msgstr "" -"თქვენს კამერაზე წვდომა ნებისმიერ დროს შეიძლება შეიცვალოს კონფიდენციალურობის " -"პარამეტრებიდან." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "აპლიკაციას სურს გამოიყენოს თქვენი კამერა." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s-ს სურს გამოიყენოს თქვენი კამერა." +msgid "Unsupported launcher type: %x" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "წვდომის უარყოფა" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "წვდომის მინიჭება" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "მივანიჭოთ %s-ს თქვენს მდებარეობაზე წვდომა?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s-ს სურს გამოიყენოს თქვენი მდებარეობა." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "მივანიჭოთ თქვენს მდებარეობაზე წვდომა?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "აპლიკაციას სურს გამოიყენოს თქვენი მდებარეობა." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "მდებარეობაზე წვდომა შეიძლება შეიცვალოს ნებისმიერ დროს კონფიდენციალურობის " "პარამეტრებიდან." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "უარყოფა" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "მიეცეს %s-ს ეკრანის ანაბეჭდების გადაღების უფლება?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s-ს სურს ეკრანის ანაბეჭდის სურვილისამებრ გადაღების უფლება მიიღოს." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "მიეცეს აპლიკაციას ეკრანის ანაბეჭდების გადაღების უფლება?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "" "აპლიკაციას სურს ეკრანის ანაბეჭდის სურვილისამებრ გადაღების უფლება მიიღოს." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "წვდომის შეცვლა შესაძლებელია ნებისმიერ დროს კონფიდენციალურობის პარამეტრებიდან." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "მოთხოვნილი პარამეტრი ვერ მოიძებნა" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "არ დაშვება" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "მიეცეს %s-ს ფონის სურათის შეცვლის უფლება?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s ფონის სურათის შეცვლის უფლებას მოითხოვს." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "მიეცეს აპლიკაციებს ფონის სურათის შეცვლის უფლება?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "აპლიკაცია ფონის სურათის შეცვლის უფლებას მოითხოვს." + +#~ msgid "Turn On Microphone?" +#~ msgstr "ჩავრთო მიკროფონი?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "თქვენს მიკროფონზე წვდომა ნებისმიერ დროს კონფიდენციალურობის პარამეტრებიდან " +#~ "შეიძლება შეიცვალოს." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "აპლიკაციას სურს გამოიყენოს თქვენი მიკროფონი." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s-ს სურს თქვენი მიკროფონი გამოიყენოს." + +#~ msgid "Turn On Speakers?" +#~ msgstr "ჩავრთო დინამიკები?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "თქვენს დინამიკებზე წვდომა შეიძლება შეიცვალოს ნებისმიერ დროს " +#~ "კონფიდენციალურობის პარამეტრებიდან." + +#~ msgid "An application wants to play sound." +#~ msgstr "აპლიკაციას ხმის გამოცემა სურს." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s-ს ხმის გამოცემა სურს." + +#~ msgid "Turn On Camera?" +#~ msgstr "ჩავრთო კამერა?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "თქვენს კამერაზე წვდომა ნებისმიერ დროს შეიძლება შეიცვალოს " +#~ "კონფიდენციალურობის პარამეტრებიდან." diff --git a/po/lt.po b/po/lt.po index 982fc35..19be96b 100644 --- a/po/lt.po +++ b/po/lt.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2020-03-07 20:26+0200\n" "Last-Translator: Moo\n" "Language-Team: Lithuanian \n" @@ -19,22 +19,22 @@ msgstr "" "(n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 2.3\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Leisti %s vykdyti fone?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s prašo, kad būtų paleidžiama automatiškai ir vykdoma fone." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s prašo, kad būtų vykdoma fone." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -42,154 +42,245 @@ msgstr "" "Leidimas \"vykdyti fone\" bet kuriuo metu gali būti pakeistas programos " "nustatymuose." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Neleisti" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Leisti" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Įjungti mikrofoną?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Leisti %s vykdyti fone?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s nori naudoti jūsų kamerą." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"Prieiga prie jūsų mikrofono bet kuriuo metu gali būti pakeista privatumo " -"nustatymuose." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Programa nori naudoti jūsų mikrofoną." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Programa nori naudoti jūsų kamerą." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s nori naudoti jūsų mikrofoną." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Įjungti garsiakalbius?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Prieiga prie jūsų garsiakalbių bet kuriuo metu gali būti pakeista privatumo " -"nustatymuose." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Programa nori atkurti garsą." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s nori atkurti garsą." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Įjungti kamerą?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"Prieiga prie jūsų kameros bet kuriuo metu gali būti pakeista privatumo " -"nustatymuose." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Programa nori naudoti jūsų kamerą." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "%s nori naudoti jūsų kamerą." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Drausti prieigą" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Suteikti prieigą" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Suteikti %s prieigą prie jūsų buvimo vietos?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s nori naudoti jūsų buvimo vietą." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Suteikti prieigą prie jūsų buvimo vietos?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Programa nori naudoti jūsų buvimo vietą." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Prieiga prie jūsų buvimo vietos bet kuriuo metu gali būti pakeista privatumo " "nustatymuose." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Drausti" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "Leisti %s nustatyti fonus?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Leisti programoms nustatyti fonus?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Programa nori naudoti jūsų mikrofoną." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Šis leidimas bet kuriuo metu gali būti pakeistas privatumo nustatymuose." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Užklaustas nustatymas nerastas" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Neleisti" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Leisti %s nustatyti fonus?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s prašo galimybės keisti fono paveikslą." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Leisti programoms nustatyti fonus?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Programa prašo galimybės keisti fono paveikslą." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Įjungti mikrofoną?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Prieiga prie jūsų mikrofono bet kuriuo metu gali būti pakeista privatumo " +#~ "nustatymuose." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Programa nori naudoti jūsų mikrofoną." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s nori naudoti jūsų mikrofoną." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Įjungti garsiakalbius?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Prieiga prie jūsų garsiakalbių bet kuriuo metu gali būti pakeista " +#~ "privatumo nustatymuose." + +#~ msgid "An application wants to play sound." +#~ msgstr "Programa nori atkurti garsą." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s nori atkurti garsą." + +#~ msgid "Turn On Camera?" +#~ msgstr "Įjungti kamerą?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Prieiga prie jūsų kameros bet kuriuo metu gali būti pakeista privatumo " +#~ "nustatymuose." diff --git a/po/nl.po b/po/nl.po index b59d351..f2edea4 100644 --- a/po/nl.po +++ b/po/nl.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2021-07-18 14:05+0200\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: Dutch\n" @@ -19,25 +19,25 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Wilt u %s toestaan om op de achtergrond te worden uitgevoerd?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "" "%s verzoekt om automatisch te mogen worden opgestart en uitgevoerd te mogen " "worden op de achtergrond." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "" "%s verzoekt om automatisch te mogen worden uitgevoerd op de achtergrond." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -45,157 +45,248 @@ msgstr "" "Het toegangsrecht ‘uitvoeren op achtergrond’ kan te allen tijde worden " "ingetrokken in de toepassingsvoorkeuren." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Niet toestaan" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Toestaan" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Microfoon aanzetten?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Wilt u %s toestaan om op de achtergrond te worden uitgevoerd?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s wil uw camera gebruiken." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"De toegang tot uw microfoon kan te allen tijde worden aangepast in de " -"privacyvoorkeuren." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Een toepassing wil uw microfoon gebruiken." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Een toepassing wil uw camera gebruiken." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s wil uw microfoon gebruiken." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Luidsprekers aanzetten?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"De toegang tot uw luidsprekers kan te allen tijde worden aangepast in de " -"privacyvoorkeuren." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Een toepassing wil geluid afspelen." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s wil geluid afspelen." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Camera aanzetten?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"De toegang tot uw camera kan te allen tijde worden aangepast in de " -"privacyvoorkeuren." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Een toepassing wil uw camera gebruiken." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "%s wil uw camera gebruiken." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Toegang weigeren" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Toegang verlenen" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Wilt u %s toegang verlenen tot uw locatie?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s wil uw locatie opvragen." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Toegang verlenen tot uw locatie?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Een toepassing wil uw locatie opvragen." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "De toegang tot uw locatie kan te allen tijde worden aangepast in de " "privacyvoorkeuren." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Weigeren" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "%s toestaan achtergronden in te stellen?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Toepassingen toestaan achtergronden in te stellen?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Een toepassing wil uw microfoon gebruiken." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Dit toegangsrecht kan te allen tijde worden aangepast in de " "privacyvoorkeuren." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "De opgevraagde instelling bestaat niet" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Niet toestaan" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "%s toestaan achtergronden in te stellen?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s verzoekt de mogelijkheid om uw achtergrondafbeelding te wijzigen." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Toepassingen toestaan achtergronden in te stellen?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "" "Een toepassing verzoekt de mogelijkheid om uw achtergrondafbeelding te " "wijzigen." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Microfoon aanzetten?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "De toegang tot uw microfoon kan te allen tijde worden aangepast in de " +#~ "privacyvoorkeuren." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Een toepassing wil uw microfoon gebruiken." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s wil uw microfoon gebruiken." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Luidsprekers aanzetten?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "De toegang tot uw luidsprekers kan te allen tijde worden aangepast in de " +#~ "privacyvoorkeuren." + +#~ msgid "An application wants to play sound." +#~ msgstr "Een toepassing wil geluid afspelen." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s wil geluid afspelen." + +#~ msgid "Turn On Camera?" +#~ msgstr "Camera aanzetten?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "De toegang tot uw camera kan te allen tijde worden aangepast in de " +#~ "privacyvoorkeuren." diff --git a/po/oc.po b/po/oc.po index ea7f30f..5a03dd1 100644 --- a/po/oc.po +++ b/po/oc.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2023-02-13 18:49+0100\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-10-26 19:05+0200\n" "Last-Translator: Quentin PAGÈS\n" "Language-Team: Occitan\n" "Language: oc\n" @@ -15,25 +15,25 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 3.2.2\n" +"X-Generator: Poedit 3.5\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Permetre a %s de s’executar en rèireplan ?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "" "%s demanda de poder s’aviar automaticament e de s’executar en rèireplan." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s demanda de s’executar en rèireplan." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,153 +41,241 @@ msgstr "" "La permission d’execucion en rèireplan se pòt modificar a tot moment als " "paramètres d’aplicacion." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Permetre pas" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Permetre" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Alucar lo microfòn ?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Permetre a %s d’utilizar la camèra ?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"L’accès al microfòn se pòt modificar a tot moment als paramètres de " -"confidencialitat." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s vòl utilizar als periferics de camèra." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Permetre a l’aplicacion d’utilizar la camèra ?" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Una aplicacion vòl utilizar lo microfòn." +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Una aplicacion vòl accedir als periferics de camèra." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s vòl utilizar lo microfòn." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Identificant de fichièr burèu sens sufix .desktop : %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Alucar los nautparlaires ?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "Fichièr desktop non valid" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." -msgstr "" -"L’accès als nautparlaires se pòt modificar a tot moment als paramètres de " -"confidencialitat." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "Entrada Desktop donada per Install() pas un fichièr clau valid" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Una aplicacion vòl legir de son." +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "Entrada Desktop donada per Install() deu aver sonque un grop" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s vòl legir de son." +msgid "Token given is invalid: %s" +msgstr "Lo geton donat es invalid : %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Alucar la camèra ?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "Entrada Desktop donada per Install() pas valida" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "" -"L’accès a la camèra se pòt modificar a tot moment als paramètres de " -"confidencialitat." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Lo fichièr desktop subrepassa la talha maximala (%d) : %s" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "L’URL donada es invalida : %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Tipe d’aviador invalid : %x" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "Tipe d’aviador pas pres en carga : %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "L’icòna de l’aviador dinamic a pas capitat la validacion" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Una aplicacion vòl utilizar la camèra." +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() non permés per l’id d’aplicacion %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Icòna de fichièr Desktop « %s » a un emplaçament non reconegut" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Icòna de fichièr Desktop « %s » a pas capitat de serializar" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Cap d’aviador dinamic existís pas amb l’id « %s »" -#: src/device.c:149 +#: src/dynamic-launcher.c:1001 #, c-format -msgid "%s wants to use your camera." -msgstr "%s vòl utilizar la camèra." +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "Fracàs de la creacion de GDesktopAppInfo per l’aviador amb id « %s »" -#: src/location.c:527 +#: src/location.c:544 msgid "Deny Access" msgstr "Refusar l’accès" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Acordar l’accès" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Donar accès a %s a vòstra localizacion ?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s vòl utilizar vòstra localizacion." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Donar accès a vòstre localizacion ?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Una aplicacion vòl utilizar vòstra localizacion." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Las règlas d’accès a la localizacion se pòdon a tot moment modificar als " "paramètres de confidencialitat." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Refusar" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Permetre a %s de prendre de capturas d’ecran ?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s vòl poder prendre de capturas d’ecran en tot temps." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Permetre a las aplicacions de prendre de capturas d’ecran ?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Una aplicacion vòl poder prendre de capturas d’ecran en tot temps." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Las permissions se pòdon modificar a tot moment als paramètres de " "confidencialitat." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Paramètre demandat pas trobat" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Permetre pas" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Permetre a %s de definir de fonzes d’ecran ?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s demanda de poder modificar l’imatge de rèireplan." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Permetre a las aplicacions de definir los fonzes d’ecran ?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Una aplicacion demanda de poder modificar l’imatge de rèireplan." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Alucar lo microfòn ?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L’accès al microfòn se pòt modificar a tot moment als paramètres de " +#~ "confidencialitat." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Una aplicacion vòl utilizar lo microfòn." + +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s vòl utilizar lo microfòn." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Alucar los nautparlaires ?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L’accès als nautparlaires se pòt modificar a tot moment als paramètres de " +#~ "confidencialitat." + +#~ msgid "An application wants to play sound." +#~ msgstr "Una aplicacion vòl legir de son." + +#~ msgid "%s wants to play sound." +#~ msgstr "%s vòl legir de son." + +#~ msgid "Turn On Camera?" +#~ msgstr "Alucar la camèra ?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "L’accès a la camèra se pòt modificar a tot moment als paramètres de " +#~ "confidencialitat." diff --git a/po/pl.po b/po/pl.po index 15422ce..d9e6711 100644 --- a/po/pl.po +++ b/po/pl.po @@ -1,15 +1,15 @@ # Polish translation for xdg-desktop-portal. -# Copyright © 2016-2022 the xdg-desktop-portal authors. +# Copyright © 2016-2024 the xdg-desktop-portal authors. # This file is distributed under the same license as the xdg-desktop-portal package. -# Piotr Drąg , 2016-2022. -# Aviary.pl , 2016-2022. +# Piotr Drąg , 2016-2024. +# Aviary.pl , 2016-2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2022-09-11 15:05+0200\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-03-03 14:55+0100\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish \n" "Language: pl\n" @@ -19,22 +19,22 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Pozwolić programowi „%s” na działanie w tle?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "Program „%s” potrzebuje być automatycznie uruchamiany i działać w tle." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "Program „%s” potrzebuje działać w tle." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -42,152 +42,229 @@ msgstr "" "Uprawnienie do działania w tle można zmienić w każdej chwili w ustawieniach " "programów." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Nie zezwalaj" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Zezwól" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Włączyć mikrofon?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Pozwolić programowi „%s” na używanie kamery?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "Program „%s” potrzebuje dostępu do kamer." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Pozwolić programowi na używanie kamery?" + +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Program potrzebuje dostępu do kamer." + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" msgstr "" -"Dostęp do mikrofonu można zmienić w każdej chwili w ustawieniach prywatności." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Program potrzebuje dostępu do mikrofonu." +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:123 +#: src/dynamic-launcher.c:306 #, c-format -msgid "%s wants to use your microphone." -msgstr "Program „%s” potrzebuje dostępu do mikrofonu." +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Włączyć głośniki?" +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" msgstr "" -"Dostęp do głośników można zmienić w każdej chwili w ustawieniach prywatności." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Program potrzebuje odtwarzać dźwięk." +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 #, c-format -msgid "%s wants to play sound." -msgstr "Program „%s” potrzebuje odtwarzać dźwięk." +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Włączyć kamerę?" +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" msgstr "" -"Dostęp do kamery można zmienić w każdej chwili w ustawieniach prywatności." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Program potrzebuje dostępu do kamery." +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:1001 #, c-format -msgid "%s wants to use your camera." -msgstr "Program „%s” potrzebuje dostępu do kamery." +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" -#: src/location.c:527 +#: src/location.c:544 msgid "Deny Access" msgstr "Odmów dostępu" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Udziel dostępu" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Udzielić dostęp do położenia programowi „%s”?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "Program „%s” potrzebuje dostępu do położenia." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Udzielić dostęp do położenia?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Program potrzebuje dostępu do położenia." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Dostęp do położenia można zmienić w każdej chwili w ustawieniach prywatności." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Odmów" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Pozwolić programowi „%s” na wykonywanie zrzutów ekranu?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" "Program „%s” potrzebuje mieć możliwość wykonywania zrzutów ekranu w dowolnym " "czasie." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Pozwolić programowi na wykonywanie zrzutów ekranu?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "" "Program potrzebuje mieć możliwość wykonywania zrzutów ekranu w dowolnym " "czasie." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "To uprawnienie można zmienić w każdej chwili w ustawieniach prywatności." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Nie odnaleziono żądanego ustawienia" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Nie zezwalaj" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Pozwolić programowi „%s” na ustawianie tła?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "Program „%s” potrzebuje mieć możliwość zmiany obrazu tła." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Pozwolić programowi na ustawianie tła?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Program potrzebuje mieć możliwość zmiany obrazu tła." + +#, c-format +#~ msgid "Allow %s to Use the Microphone?" +#~ msgstr "Pozwolić programowi „%s” na używanie mikrofonu?" + +#, c-format +#~ msgid "%s wants to access recording devices." +#~ msgstr "Program „%s” potrzebuje dostępu do urządzeń nagrywających." + +#~ msgid "Allow app to Use the Microphone?" +#~ msgstr "Pozwolić programowi na używanie mikrofonu?" + +#~ msgid "An app wants to access recording devices." +#~ msgstr "Program potrzebuje dostępu do urządzeń nagrywających." + +#, c-format +#~ msgid "Allow %s to Use the Speakers?" +#~ msgstr "Pozwolić programowi „%s” na używanie głośników?" + +#, c-format +#~ msgid "%s wants to access audio devices." +#~ msgstr "Program „%s” potrzebuje dostępu do urządzeń dźwiękowych." + +#~ msgid "Allow app to Use the Speakers?" +#~ msgstr "Pozwolić programowi na używanie głośników?" + +#~ msgid "An app wants to access audio devices." +#~ msgstr "Program potrzebuje dostępu do urządzeń dźwiękowych." diff --git a/po/pt.po b/po/pt.po index fc16952..db2fa44 100644 --- a/po/pt.po +++ b/po/pt.po @@ -1,195 +1,288 @@ # Portuguese translation for xdg-desktop-portal. -# Copyright (C) 2022 xdg-desktop-portal's COPYRIGHT HOLDER +# Copyright (C) 2024 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. # Juliano de Souza Camargo , 2022. +# Hugo Carvalho , 2022, 2023, 2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2022-01-20 10:40-0300\n" -"Last-Translator: Juliano de Souza Camargo \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-10-16 14:24+0100\n" +"Last-Translator: Hugo Carvalho \n" "Language-Team: Portuguese < https://l10n.gnome.org/teams/pt/>\n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" -"X-Generator: Gtranslator 40.0\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.5\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" -msgstr "Permitir a %s correr em segundo plano?" +msgstr "Permitir que %s seja executado em segundo plano?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." -msgstr "%s requisita iniciar automaticamente e correr em segundo plano." +msgstr "%s solicita ser iniciado automaticamente e executado em segundo plano." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." -msgstr "%s requisita correr em segundo plano." +msgstr "%s solicita ser executado em segundo plano." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." msgstr "" -"Pode alterar, em qualquer altura, a permissão para “correr em segundo plano” " -"pelas definições da aplicação." +"A permissão 'executar em segundo plano' pode ser alterada a qualquer altura " +"a partir das definições da aplicação." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Não permitir" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Permitir" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Ligar o microfone?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Permitir que %s utilize a câmara?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"Pode alterar, em qualquer altura, o acesso ao microfone pelas definições de " -"privacidade." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s pretende aceder a dispositivos de câmara." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Uma aplicação requisita o uso de seu microfone." +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Permitir que a aplicação utilize a câmara?" -#: src/device.c:123 +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Uma aplicação pretende aceder a dispositivos de câmara." + +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s requisita o uso de seu microfone." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Sufixo .desktop em falta no id do ficheiro desktop: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Ligar os altifalantes?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "A ID do ficheiro desktop não é válida" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Pode alterar, em qualquer altura, o acesso aos altifalantes pelas definições " -"de privacidade." +"A entrada desktop fornecida para Install() não é um ficheiro de chave válido" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Uma aplicação requisita reproduzir som." +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "A entrada desktop fornecida para Install() deve ter apenas um grupo" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s requisita reproduzir som." +msgid "Token given is invalid: %s" +msgstr "O token fornecido é inválido: %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Ligar a câmara?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "A entrada desktop fornecida para Install() não é válida" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "" -"Pode alterar, em qualquer altura, o acesso à câmara pelas definições de " -"privacidade." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "O ficheiro desktop excede o tamanho máximo (%d): %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Uma aplicação requisita o uso de sua câmara." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "O URL fornecido é inválido: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Tipo de lançador inválido: %x" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s requisita o uso de sua câmara." +msgid "Unsupported launcher type: %x" +msgstr "Tipo de lançador não suportado: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Falha na validação do ícone do lançador dinâmico" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() não permitido para o id da aplicação %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "O ícone do ficheiro desktop '%s' está num caminho irreconhecível" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Falha na serialização do ícone do ficheiro desktop '%s'" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Não existe nenhum lançador dinâmico com o id '%s'" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "Falha ao criar GDesktopAppInfo para lançador com id '%s'" + +#: src/location.c:544 msgid "Deny Access" msgstr "Negar acesso" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Conceder acesso" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Permitir que %s aceda à sua localização?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s quer ter acesso à sua localização." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Conceder acesso à sua localização?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." -msgstr "Uma aplicação requisita acesso à sua localização." +msgstr "Uma aplicação quer ter acesso à sua localização." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" -"Pode alterar, em qualquer altura, o acesso à localização pelas definições de " -"privacidade." +"O acesso à localização pode ser alterado a qualquer altura a partir das " +"definições de privacidade." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Negar" -#: src/screenshot.c:256 -#, fuzzy, c-format +#: src/screenshot.c:267 +#, c-format msgid "Allow %s to Take Screenshots?" -msgstr "Permitir que %s defina o fundo?" +msgstr "Permitir que %s tire capturas de ecrã?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." -msgstr "" +msgstr "%s quer ser capaz de capturar o ecrã a qualquer altura." -#: src/screenshot.c:265 -#, fuzzy +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" -msgstr "Permitir que aplicações definam o fundo?" +msgstr "Permitir que as aplicações tirem capturas de ecrã?" -#: src/screenshot.c:266 -#, fuzzy +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." -msgstr "Uma aplicação requisita o uso de seu microfone." +msgstr "" +"Uma aplicação quer ser capaz de tirar capturas de ecrã a qualquer altura." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" -"Pode alterar, em qualquer altura, esta permissão pelas definições de " -"privacidade." +"Esta permissão pode ser alterada a qualquer altura a partir das definições " +"de privacidade." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" -msgstr "Definição solicitada não encontrada" +msgstr "A definição solicitada não foi encontrada" + +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Não permitir" -#: src/wallpaper.c:205 +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" -msgstr "Permitir que %s defina o fundo?" +msgstr "Permitir que %s defina planos de fundo?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." -msgstr "%s requisita poder alterar a imagem de fundo." +msgstr "%s está a solicitar a capacidade de poder alterar a imagem de fundo." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" -msgstr "Permitir que aplicações definam o fundo?" +msgstr "Permitir que aplicações definam planos de fundo?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." -msgstr "Uma aplicação requisita poder alterar a imagem de fundo." +msgstr "" +"Uma aplicação está a solicitar a capacidade de poder alterar a imagem de " +"fundo." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Ligar o microfone?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Pode alterar, em qualquer altura, o acesso ao microfone pelas definições " +#~ "de privacidade." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Uma aplicação requisita o uso de seu microfone." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s requisita o uso de seu microfone." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Ligar os altifalantes?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Pode alterar, em qualquer altura, o acesso aos altifalantes pelas " +#~ "definições de privacidade." + +#~ msgid "An application wants to play sound." +#~ msgstr "Uma aplicação requisita reproduzir som." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s requisita reproduzir som." + +#~ msgid "Turn On Camera?" +#~ msgstr "Ligar a câmara?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Pode alterar, em qualquer altura, o acesso à câmara pelas definições de " +#~ "privacidade." diff --git a/po/pt_BR.po b/po/pt_BR.po index a34e9ed..8344df9 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -1,39 +1,39 @@ # Brazilian Portuguese translation for xdg-desktop-portal. -# Copyright (C) 2019 xdg-desktop-portal's COPYRIGHT HOLDER +# Copyright (C) 2024 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. -# Rafael Fontenelle , 2016-2019. +# Rafael Fontenelle , 2016-2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2019-11-24 22:29-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-04-05 16:40-0300\n" "Last-Translator: Rafael Fontenelle \n" -"Language-Team: Brazilian Portuguese \n" +"Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" -"X-Generator: Gtranslator 3.32.0\n" +"X-Generator: Gtranslator 46.0\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Permitir que %s execute em segundo plano?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s solicita ser iniciado automaticamente e executado em segundo plano." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s solicita ser executado em segundo plano." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,158 +41,290 @@ msgstr "" "A permissão “executar em segundo plano” pode ser alterada a qualquer tempo a " "partir das configurações do aplicativo." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Não permitir" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Permitir" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Ligar o microfone?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Permitir que %s use a câmera?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"Acesso ao seu microfone pode ser alterado a qualquer tempo a partir das " -"configurações de privacidades." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s deseja acessar dispositivos de câmera." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Um aplicativo deseja usar seu microfone." +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Permitir que o aplicativo use a câmera?" -#: src/device.c:123 +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Um aplicativo deseja acessar dispositivos de câmera." + +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s deseja usar seu microfone." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Faltando sufixo .desktop no id do arquivo desktop: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Ligar o alto-falante?" +#: src/dynamic-launcher.c:133 +#, fuzzy, c-format +msgid "Desktop file id is not valid" +msgstr "A entrada desktop fornecida para Install() não é válida" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Acesso ao seu alto-falante pode ser alterado a qualquer tempo a partir das " -"configurações de privacidades." +"A entrada desktop fornecida para Install() não é um arquivo de chave válido" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Um aplicativo deseja reproduzir som." +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "A entrada desktop fornecida para Install() deve ter apenas um grupo" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s deseja reproduzir som." +msgid "Token given is invalid: %s" +msgstr "O token fornecido é inválido: %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Ligar a câmera?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "A entrada desktop fornecida para Install() não é válida" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "" -"Acesso à sua câmera pode ser alterado a qualquer tempo a partir das " -"configurações de privacidades." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "O arquivo desktop excede o tamanho máximo (%d): %s" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "URL fornecida é inválida: %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Um aplicativo deseja usar sua câmera." +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Tipo de lançador inválido: %x" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s deseja usar sua câmera." +msgid "Unsupported launcher type: %x" +msgstr "Tipo de lançador não suportado: %x" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "O ícone do lançador dinâmico falhou na validação" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() não permitido para o id de aplicativo %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "O ícone do arquivo desktop “%s” está em um caminho irreconhecível" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "O ícone do arquivo desktop “%s” falhou na serialização" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Nenhum lançador dinâmico existe com o id “%s”" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "Falha ao criar GDesktopAppInfo para lançador com id “%s”" + +#: src/location.c:544 msgid "Deny Access" msgstr "Negar acesso" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Conceder acesso" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Dar %s acesso a sua localização?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s deseja usar sua localização." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Conceder acesso à sua localização?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Um aplicativo deseja usar sua localização." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Acesso à localização pode ser alterado a qualquer tempo a partir das " "configurações de privacidades." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Negar" -#: src/screenshot.c:256 -#, fuzzy, c-format +#: src/screenshot.c:267 +#, c-format msgid "Allow %s to Take Screenshots?" -msgstr "Permitir que %s defina planos de fundo?" +msgstr "Permitir que %s faça capturas de tela?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." -msgstr "" +msgstr "%s deseja ser capaz de capturar a tela a qualquer momento." -#: src/screenshot.c:265 -#, fuzzy +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" -msgstr "Permitir que aplicativos definam planos de fundo?" +msgstr "Permitir que aplicativos façam capturas de tela?" -#: src/screenshot.c:266 -#, fuzzy +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." -msgstr "Um aplicativo deseja usar seu microfone." +msgstr "" +"Um aplicativo deseja ser capaz de fazer capturas de tela a qualquer momento." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Essa permissão pode ser alterada a qualquer momento a partir das " "configurações de privacidades." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "A configuração requisitada não foi encontrada" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Não permitir" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Permitir que %s defina planos de fundo?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "" "%s está solicitando a capacidade de alterar a imagem de plano de fundo." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Permitir que aplicativos definam planos de fundo?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "" "Um aplicativo está solicitando a capacidade de alterar a imagem de plano de " "fundo." + +#, c-format +#~ msgid "Allow %s to Use the Microphone?" +#~ msgstr "Permitir que %s use o microfone?" + +#, c-format +#~ msgid "%s wants to access recording devices." +#~ msgstr "%s deseja acessar dispositivos de gravação." + +#~ msgid "Allow app to Use the Microphone?" +#~ msgstr "Permitir que o aplicativo use o microfone?" + +#~ msgid "An app wants to access recording devices." +#~ msgstr "Um aplicativo deseja acessar dispositivos de gravação." + +#, c-format +#~ msgid "Allow %s to Use the Speakers?" +#~ msgstr "Permitir que %s use os alto-falantes?" + +#, c-format +#~ msgid "%s wants to access audio devices." +#~ msgstr "%s deseja acessar dispositivos de áudio." + +#~ msgid "Allow app to Use the Speakers?" +#~ msgstr "Permitir que o aplicativo use os alto-falantes?" + +#~ msgid "An app wants to access audio devices." +#~ msgstr "Um aplicativo deseja acessar os dispositivos de áudio." + +#, c-format +#~ msgid "Desktop file id missing app id prefix '%s.': %s" +#~ msgstr "" +#~ "Faltando prefixo de id de aplicativo no id do arquivo desktop: “%s.”: %s" + +#, c-format +#~ msgid "Desktop entry given to Install() must not use --file-forwarding" +#~ msgstr "" +#~ "A entrada desktop fornecida para Install() não deve usar --file-forwarding" + +#, c-format +#~ msgid "DynamicLauncher install not supported for: %s" +#~ msgstr "Instalação por DynamicLauncher não suportada para: %s" + +#~ msgid "Turn On Microphone?" +#~ msgstr "Ligar o microfone?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Acesso ao seu microfone pode ser alterado a qualquer tempo a partir das " +#~ "configurações de privacidades." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Um aplicativo deseja usar seu microfone." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s deseja usar seu microfone." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Ligar o alto-falante?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Acesso ao seu alto-falante pode ser alterado a qualquer tempo a partir " +#~ "das configurações de privacidades." + +#~ msgid "An application wants to play sound." +#~ msgstr "Um aplicativo deseja reproduzir som." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s deseja reproduzir som." + +#~ msgid "Turn On Camera?" +#~ msgstr "Ligar a câmera?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Acesso à sua câmera pode ser alterado a qualquer tempo a partir das " +#~ "configurações de privacidades." diff --git a/po/ro.po b/po/ro.po index 48298f2..3c6d2ad 100644 --- a/po/ro.po +++ b/po/ro.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2022-02-18 10:51+0000\n" "Last-Translator: Sergiu Bivol \n" "Language-Team: Romanian\n" @@ -18,22 +18,22 @@ msgstr "" "20)) ? 1 : 2;\n" "X-Generator: Lokalize 21.12.2\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Permiteți %s să ruleze în fundal?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s cere să fie pornit automat și să ruleze în fundal." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s cere să ruleze în fundal." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,155 +41,246 @@ msgstr "" "Permisiunea „rulează în fundal” poate fi schimbată în orice moment din " "configurările aplicației." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Nu permite" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Permite" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Porniți microfonul?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Permiteți %s să ruleze în fundal?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "%s vrea să vă folosească camera." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"Accesul la microfon poate fi schimbat oricând din configurările de " -"confidențialitate." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "O aplicație vrea să vă folosească microfonul." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "O aplicație vrea să vă folosească camera." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s vrea să vă folosească microfonul." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Porniți difuzoarele?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Accesul la difuzoare poate fi schimbat oricând din configurările de " -"confidențialitate." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "O aplicație dorește să redea sunet." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s dorește să redea sunet." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Porniți camera?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"Accesul la cameră poate fi schimbat oricând din configurările de " -"confidențialitate." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "O aplicație vrea să vă folosească camera." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "%s vrea să vă folosească camera." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Refuză accesul" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Permite accesul" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Permiteți accesul %s la localizare?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s vrea să vă folosească localizarea." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Permiteți accesul la localizare?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "O aplicație vrea să vă folosească localizarea." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Accesul la localizare poate fi schimbat oricând din configurările de " "confidențialitate." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Refuză" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "Permiteți %s să stabilească fundaluri?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Permiteți aplicațiilor să stabilească fundaluri?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "O aplicație vrea să vă folosească microfonul." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Această permisiune poate fi schimbată oricând din configurările de " "confidențialitate." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Configurarea cerută nu a fost găsită" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Nu permite" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Permiteți %s să stabilească fundaluri?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s cere să poată schimba imaginea de fundal." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Permiteți aplicațiilor să stabilească fundaluri?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "O aplicație cere să poată schimba imaginea de fundal." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Porniți microfonul?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Accesul la microfon poate fi schimbat oricând din configurările de " +#~ "confidențialitate." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "O aplicație vrea să vă folosească microfonul." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s vrea să vă folosească microfonul." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Porniți difuzoarele?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Accesul la difuzoare poate fi schimbat oricând din configurările de " +#~ "confidențialitate." + +#~ msgid "An application wants to play sound." +#~ msgstr "O aplicație dorește să redea sunet." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s dorește să redea sunet." + +#~ msgid "Turn On Camera?" +#~ msgstr "Porniți camera?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Accesul la cameră poate fi schimbat oricând din configurările de " +#~ "confidențialitate." diff --git a/po/ru.po b/po/ru.po index ac94026..aff6b5e 100644 --- a/po/ru.po +++ b/po/ru.po @@ -3,192 +3,334 @@ # This file is distributed under the same license as the xdg-desktop-portal package. # Артемий Судаков , 2020. # +# SPDX-FileCopyrightText: 2024 SPDX-FileCopyrightText : msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2022-10-04 12:51+0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2025-02-07 14:00+0300\n" "Last-Translator: Aleksandr Melman \n" -"Language-Team: Russian \n" +"Language-Team: RU\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 3.1.1\n" +"X-Generator: Poedit 3.5\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" -msgstr "Разрешить %s работать в фоне?" +msgstr "Разрешить приложению \"%s\" работать в фоне?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." -msgstr "%s запрашивает доступ на автоматический запуск и работу в фоне." +msgstr "" +"Приложение \"%s\" запрашивает доступ на автоматический запуск и работу в " +"фоне." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." -msgstr "%s запрашивает доступ на работу в фоне." +msgstr "Приложение \"%s\" запрашивает доступ на работу в фоне." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." msgstr "" -"Разрешение на \"работу в фоне\" может быть изменено в любое время из меню " -"настроек приложения." +"Разрешение на ‘работу в фоне’ можно изменить в любой момент в настройках " +"приложения." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Запретить" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Разрешить" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Включить микрофон?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Разрешить приложению \"%s\" использовать камеру?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"Доступ к микрофону может быть изменен в любое время из меню настроек " -"конфиденциальности." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "Приложение \"%s\" хочет получить доступ к устройствам камеры." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Разрешить приложению использовать камеру?" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Приложение запрашивает доступ к микрофону." +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Приложение хочет получить доступ к устройствам камеры." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s запрашивает доступ к микрофону." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "В идентификаторе desktop-файла отсутствует суффикс .desktop: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Включить динамики?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "Идентификатор desktop-файла недействителен" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Доступ к динамикам может быть изменен в любое время из меню настроек " -"конфиденциальности." +"Desktop-запись, переданная в Install(), не содержит действительного файла " +"ключа" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Приложение хочет воспроизвести звук." +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" +"Desktop-запись, переданная в Install(), должна иметь только одну группу" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s хочет воспроизвести звук." +msgid "Token given is invalid: %s" +msgstr "Указанный токен недействителен: %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Включить камеру?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "Desktop-запись, переданная в Install(), недействительна" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "" -"Доступ к камере может быть изменен в любое время из меню настроек " -"конфиденциальности." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Desktop-файл превышает максимальный размер (%d): %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Приложение запрашивает доступ к камере." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "Указанный URL недействителен: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Недействительный тип программы запуска: %x" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s запрашивает доступ к камере." +msgid "Unsupported launcher type: %x" +msgstr "Неподдерживаемый тип программы запуска: %x" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Значок динамической программы запуска не прошел проверку" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() не разрешен для идентификатора приложения %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Значок desktop-файла '%s' находится по нераспознанному пути" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Не удалось сериализовать значок desktop-файла '%s'" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Не существует динамической программы запуска с идентификатором '%s'" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" +"Не удалось создать GDesktopAppInfo для программы запуска с идентификатором " +"'%s'" + +#: src/location.c:544 msgid "Deny Access" -msgstr "Отклонить запрос" +msgstr "Отказать в доступе" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Предоставить доступ" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" -msgstr "Дать %s доступ к вашему местоположению?" +msgstr "Дать приложению \"%s\" доступ к вашему местоположению?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." -msgstr "%s запрашивает доступ к использоованию сервисов геолокации." +msgstr "Приложение \"%s\" хочет использовать ваше местоположение." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Предоставить доступ к вашему местоположению?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." -msgstr "Приложение запрашивает доступ к сервисам геолокации." +msgstr "Приложение хочет использовать ваше местоположение." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" -"Доступ к сервисам местоположения может быть изменен в любое время из меню " -"настроек конфиденциальности." +"Доступ к местоположению можно изменить в любой момент в настройках " +"конфиденциальности." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" -msgstr "Отклонить" +msgstr "Отказать" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" -msgstr "Разрешить %s делать снимки экрана?" +msgstr "Разрешить приложению \"%s\" делать снимки экрана?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." -msgstr "%s хочет иметь возможность делать снимки экрана в любое время." +msgstr "" +"Приложение \"%s\" хочет иметь возможность делать снимки экрана в любое время." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Разрешить приложениям делать снимки экрана?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Приложение хочет иметь возможность делать снимки экрана в любое время." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" -"Это разрешение может быть изменено в любое время из меню настроек " -"конфиденциальности." +"Это разрешение можно изменить в любой момент в настройках конфиденциальности." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Запрашиваемая настройка не найдена" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "Устройство недоступно" + +#: src/usb.c:1342 +msgid "Not allowed" +msgstr "Запрещено" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" -msgstr "Разрешить %s устанавливать фоновые изображения?" +msgstr "Разрешить приложению \"%s\" устанавливать фоновые изображения?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." -msgstr "%s запрашивает возможность изменения фонового изображения." +msgstr "" +"Приложение \"%s\" запрашивает возможность изменения фонового изображения." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Разрешить приложениям устанавливать фоновые изображения?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Приложение запрашивает возможность изменения фонового изображения." + +#, c-format +#~ msgid "Allow %s to Use the Microphone?" +#~ msgstr "Разрешить приложению \"%s\" использовать микрофон?" + +#, c-format +#~ msgid "%s wants to access recording devices." +#~ msgstr "Приложение \"%s\" хочет получить доступ к записывающим устройствам." + +#~ msgid "Allow app to Use the Microphone?" +#~ msgstr "Разрешить приложению использовать микрофон?" + +#~ msgid "An app wants to access recording devices." +#~ msgstr "Приложение хочет получить доступ к записывающим устройствам." + +#, c-format +#~ msgid "Allow %s to Use the Speakers?" +#~ msgstr "Разрешить приложению \"%s\" использовать динамики?" + +#, c-format +#~ msgid "%s wants to access audio devices." +#~ msgstr "Приложение \"%s\" хочет получить доступ к аудиоустройствам." + +#~ msgid "Allow app to Use the Speakers?" +#~ msgstr "Разрешить приложению использовать динамики?" + +#~ msgid "An app wants to access audio devices." +#~ msgstr "Приложение хочет получить доступ к аудиоустройствам." + +#, c-format +#~ msgid "Desktop file id missing app id prefix '%s.': %s" +#~ msgstr "" +#~ "В идентификаторе desktop-файла отсутствует префикс идентификатора " +#~ "приложения '%s.': %s" + +#, c-format +#~ msgid "Desktop entry given to Install() must not use --file-forwarding" +#~ msgstr "" +#~ "Desktop-запись, переданная в Install(), не должна использовать --file-" +#~ "forwarding" + +#, c-format +#~ msgid "DynamicLauncher install not supported for: %s" +#~ msgstr "Установка DynamicLauncher не поддерживается для: %s" + +#~ msgid "Turn On Microphone?" +#~ msgstr "Включить микрофон?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Доступ к микрофону можно изменить в любой момент в настройках " +#~ "конфиденциальности." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Приложение хочет использовать микрофон." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "Приложение \"%s\" хочет использовать микрофон." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Включить динамики?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Доступ к динамикам можно изменить в любой момент в настройках " +#~ "конфиденциальности." + +#~ msgid "An application wants to play sound." +#~ msgstr "Приложение хочет воспроизвести звук." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "Приложение \"%s\" хочет воспроизвести звук." + +#~ msgid "Turn On Camera?" +#~ msgstr "Включить камеру?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Доступ к камере можно изменить в любой момент в настройках " +#~ "конфиденциальности." diff --git a/po/sk.po b/po/sk.po index 8746ff8..3b6a087 100644 --- a/po/sk.po +++ b/po/sk.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2020-09-22 14:32+0200\n" "Last-Translator: Dušan Kazik \n" "Language-Team: Slovak \n" @@ -18,22 +18,22 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n" "X-Generator: Poedit 2.4.1\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Umožniť aplikácii %s spustenie na pozadí?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "Aplikácia %s požaduje automatické spustenie a beh na pozadí." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "Aplikácia %s požaduje spustenie na pozadí." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -41,150 +41,242 @@ msgstr "" "Oprávnenie „spustenie na pozadí“ môže byť kedykoľvek zmenené z nastavení " "aplikácií." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Nepovoliť" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Povoliť" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Zapnúť mikrofón?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Umožniť aplikácii %s spustenie na pozadí?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "Aplikácia %s sa pokúša použiť vašu kameru." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"Prístup k vášmu mikrofónu môže byť kedykoľvek zmenený z nastavení súkromia." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Aplikácia sa pokúša použiť váš mikrofón." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Aplikácia sa pokúša použiť vašu kameru." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "Aplikácia %s sa pokúša použiť váš mikrofón." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Zapnúť reproduktory?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Prístup k vašim reproduktorom môže byť kedykoľvek zmenený z nastavení " -"súkromia." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Aplikácia sa pokúša prehrať zvuk." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "Aplikácia %s sa pokúša prehrať zvuk." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Zapnúť kameru?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"Prístup k vašej kamere môže byť zmenený kedykoľvek z nastavení súkromia." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Aplikácia sa pokúša použiť vašu kameru." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "Aplikácia %s sa pokúša použiť vašu kameru." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "Zamietnuť prístup" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Udeliť prístup" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Umožniť aplikácii %s prístup k vašej polohe?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "Aplikácia %s sa pokúša použiť vašu polohu." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Udeliť prístup k vašej polohe?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Aplikácia sa pokúša použiť vašu polohu." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Prístup k vašej polohe môže byť kedykoľvek zmenený z nastavení súkromia." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Odmietnuť" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "Umožniť aplikácii %s nastavovať pozadia?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Umožniť aplikáciám nastavovať pozadia?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Aplikácia sa pokúša použiť váš mikrofón." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "Toto oprávnenie môže byť kedykoľvek zmenené z nastavení súkromia." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Požadované nastavenie sa nenašlo" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Nepovoliť" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Umožniť aplikácii %s nastavovať pozadia?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "Aplikácia %s požaduje povolenie na zmenu obrázku pozadia." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Umožniť aplikáciám nastavovať pozadia?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Aplikácia požaduje povolenie na zmenu obrázku pozadia." + +#~ msgid "Turn On Microphone?" +#~ msgstr "Zapnúť mikrofón?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Prístup k vášmu mikrofónu môže byť kedykoľvek zmenený z nastavení " +#~ "súkromia." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Aplikácia sa pokúša použiť váš mikrofón." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "Aplikácia %s sa pokúša použiť váš mikrofón." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Zapnúť reproduktory?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Prístup k vašim reproduktorom môže byť kedykoľvek zmenený z nastavení " +#~ "súkromia." + +#~ msgid "An application wants to play sound." +#~ msgstr "Aplikácia sa pokúša prehrať zvuk." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "Aplikácia %s sa pokúša prehrať zvuk." + +#~ msgid "Turn On Camera?" +#~ msgstr "Zapnúť kameru?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Prístup k vašej kamere môže byť zmenený kedykoľvek z nastavení súkromia." diff --git a/po/sl.po b/po/sl.po new file mode 100644 index 0000000..58b7b4d --- /dev/null +++ b/po/sl.po @@ -0,0 +1,241 @@ +# Slovenian translation for xdg-desktop-portal. +# Copyright (C) 2024 xdg-desktop-portal's COPYRIGHT HOLDER +# This file is distributed under the same license as the xdg-desktop-portal package. +# +# Martin , 2024,2025. +# +msgid "" +msgstr "" +"Project-Id-Version: xdg-desktop-portal main\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2025-02-10 09:32+0100\n" +"Last-Translator: Martin Srebotnjak \n" +"Language-Team: Slovenian GNOME Translation Team \n" +"Language: sl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || " +"n%100==4 ? 3 : 0) ;\n" +"X-Generator: Poedit 2.2.1\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: src/background.c:827 +#, c-format +msgid "Allow %s to run in the background?" +msgstr "Dovolite, da %s deluje v ozadju?" + +#: src/background.c:831 +#, c-format +msgid "%s requests to be started automatically and run in the background." +msgstr "%s zahteve samodejni zagon in izvajanje v ozadju." + +#: src/background.c:833 +#, c-format +msgid "%s requests to run in the background." +msgstr "%s zahteve izvajanje v ozadju." + +#: src/background.c:834 +msgid "" +"The ‘run in background’ permission can be changed at any time from the " +"application settings." +msgstr "" +"Dovoljenje »zaženi v ozadju« lahko kadar koli spremenite v nastavitvah " +"programa." + +#: src/background.c:838 +msgid "Don't allow" +msgstr "Ne dovoli" + +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 +msgid "Allow" +msgstr "Dovoli" + +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Dovolite %s uporabo kamere?" + +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s želi dostopati do naprav s kamero." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Ali programu dovolite uporabo kamere?" + +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Program želi dostopati do naprav s kamero." + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "ID-ju namizne datoteke manjka pripona .desktop: %s" + +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "ID namizne datoteke ni veljaven" + +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" +"Vnos na namizju, podan funkciji Install(), ni veljavna datoteka s ključem" + +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "Vnos na namizju, podan funkciji Install(), mora imeti samo eno skupino" + +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "Podani žeton je neveljaven: %s" + +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "Vnos na namizju, podan funkciji Install(), ni veljaven" + +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Namizna datoteka presega največjo velikost (%d): %s" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "Navedeni URL je neveljaven: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Neveljavna vrsta zaganjalnika: %x" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "Nepodprta vrsta zaganjalnika: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Preverjanje veljavnosti ikone dinamičnega zaganjalnika ni uspelo" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() ni dovoljeno za ID programa %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Ikona namizne datoteke »%s« je na neprepoznani poti" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Ikone namizne datoteke »%s« ni bilo mogoče serializirati" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Noben dinamični zaganjalnik ne obstaja z ID-jem »%s«" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "Za zaganjalnik z ID-jem »%s« ni uspelo ustvariti GDesktopAppInfo" + +#: src/location.c:544 +msgid "Deny Access" +msgstr "Zavrni dostop" + +#: src/location.c:546 +msgid "Grant Access" +msgstr "Dovoli dostop" + +#: src/location.c:566 +#, c-format +msgid "Give %s Access to Your Location?" +msgstr "Omogočite %s dostop do svoje lokacije?" + +#: src/location.c:571 +#, c-format +msgid "%s wants to use your location." +msgstr "%s želi uporabiti vašo lokacijo." + +#: src/location.c:580 +msgid "Grant Access to Your Location?" +msgstr "Odobrite dostop do svoje lokacije?" + +#: src/location.c:581 +msgid "An application wants to use your location." +msgstr "Program želi uporabiti vašo lokacijo." + +#: src/location.c:584 +msgid "Location access can be changed at any time from the privacy settings." +msgstr "" +"Dostop do trenutnega mesta je mogoče spremeniti kadarkoli med nastavitvami " +"zasebnosti." + +#: src/screenshot.c:245 src/wallpaper.c:185 +msgid "Deny" +msgstr "Zavrni" + +#: src/screenshot.c:267 +#, c-format +msgid "Allow %s to Take Screenshots?" +msgstr "Dovolite %s snemanja posnetkov zaslona?" + +#: src/screenshot.c:268 +#, c-format +msgid "%s wants to be able to take screenshots at any time." +msgstr "%s želi imeti možnost, da kadar koli posname posnetke zaslona." + +#: src/screenshot.c:276 +msgid "Allow Applications to Take Screenshots?" +msgstr "Dovolite programom, da naredijo posnetke zaslona?" + +#: src/screenshot.c:277 +msgid "An application wants to be able to take screenshots at any time." +msgstr "Program želi kadar koli narediti posnetke zaslona." + +#: src/screenshot.c:280 src/wallpaper.c:220 +msgid "This permission can be changed at any time from the privacy settings." +msgstr "To dovoljenje lahko kadar koli spremenite v nastavitvah zasebnosti." + +#: src/settings.c:187 src/settings.c:223 +msgid "Requested setting not found" +msgstr "Zahtevane nastavitve ni bilo mogoče najti" + +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Ne dovoli" + +#: src/wallpaper.c:207 +#, c-format +msgid "Allow %s to Set Backgrounds?" +msgstr "Dovolite %s nastavitev ozadij?" + +#: src/wallpaper.c:208 +#, c-format +msgid "%s is requesting to be able to change the background image." +msgstr "%s zahteva, da lahko spremeni sliko ozadja." + +#: src/wallpaper.c:217 +msgid "Allow Applications to Set Backgrounds?" +msgstr "Dovolite programom, da nastavijo ozadja?" + +#: src/wallpaper.c:218 +msgid "An application is requesting to be able to change the background image." +msgstr "Program zahteva, da lahko spremeni sliko ozadja." diff --git a/po/sr.po b/po/sr.po index 85e9048..5e3a354 100644 --- a/po/sr.po +++ b/po/sr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2016-09-14 12:54+0200\n" "Last-Translator: Мирослав Николић \n" "Language-Team: српски \n" @@ -18,22 +18,22 @@ msgstr "" "Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : " "n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "" -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "" -#: src/background.c:754 +#: src/background.c:834 #, fuzzy msgid "" "The ‘run in background’ permission can be changed at any time from the " @@ -42,159 +42,249 @@ msgstr "" "Приступ вашим звучницима се може изменити у било које време из подешавања " "приватности." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Да укључим микрофон?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "„%s“ жели да користи вашу камерицу." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" msgstr "" -"Приступ вашем микрофону се може изменити у било које време из подешавања " -"приватности." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Један програм жели да користи ваш микрофон." +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "Један програм жели да користи вашу камерицу." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "„%s“ жели да користи ваш микрофон." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Да укључим звучнике?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" msgstr "" -"Приступ вашим звучницима се може изменити у било које време из подешавања " -"приватности." -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Један програм жели да пусти звук." +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "„%s“ жели да пусти звук." +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Да укључим камерицу?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" msgstr "" -"Приступ вашој камерици се може изменити у било које време из подешавања " -"приватности." -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Један програм жели да користи вашу камерицу." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:586 #, c-format -msgid "%s wants to use your camera." -msgstr "„%s“ жели да користи вашу камерицу." +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "" -#: src/location.c:556 +#: src/location.c:571 #, fuzzy, c-format msgid "%s wants to use your location." msgstr "„%s“ жели да користи вашу камерицу." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "" -#: src/location.c:566 +#: src/location.c:581 #, fuzzy msgid "An application wants to use your location." msgstr "Један програм жели да користи вашу камерицу." -#: src/location.c:569 +#: src/location.c:584 #, fuzzy msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Приступ вашим звучницима се може изменити у било које време из подешавања " "приватности." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "Један програм жели да пусти звук." -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "Један програм жели да користи ваш микрофон." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 #, fuzzy msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Приступ вашим звучницима се може изменити у било које време из подешавања " "приватности." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +msgid "Not allowed" +msgstr "" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "" -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 #, fuzzy msgid "Allow Applications to Set Backgrounds?" msgstr "Један програм жели да пусти звук." -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "" + +#~ msgid "Turn On Microphone?" +#~ msgstr "Да укључим микрофон?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Приступ вашем микрофону се може изменити у било које време из подешавања " +#~ "приватности." + +#~ msgid "An application wants to use your microphone." +#~ msgstr "Један програм жели да користи ваш микрофон." + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "„%s“ жели да користи ваш микрофон." + +#~ msgid "Turn On Speakers?" +#~ msgstr "Да укључим звучнике?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Приступ вашим звучницима се може изменити у било које време из подешавања " +#~ "приватности." + +#~ msgid "An application wants to play sound." +#~ msgstr "Један програм жели да пусти звук." + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "„%s“ жели да пусти звук." + +#~ msgid "Turn On Camera?" +#~ msgstr "Да укључим камерицу?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "" +#~ "Приступ вашој камерици се може изменити у било које време из подешавања " +#~ "приватности." diff --git a/po/sv.po b/po/sv.po index 82d880a..3ad7d6a 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1,15 +1,15 @@ # Swedish translation for xdg-desktop-portal. -# Copyright © 2016, 2020, 2022 xdg-desktop-portal's COPYRIGHT HOLDER +# Copyright © 2016, 2020, 2022, 2023, 2024 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. # Sebastian Rasmussen , 2016. -# Anders Jonsson , 2020, 2022. +# Anders Jonsson , 2020, 2022, 2023, 2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2022-09-10 15:25+0200\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-10-07 18:22+0200\n" "Last-Translator: Anders Jonsson \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -17,24 +17,24 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.1.1\n" +"X-Generator: Poedit 3.5\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Tillåt %s att köra i bakgrunden?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s begär att startas automatiskt och köras i bakgrunden." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s begär att köras i bakgrunden." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -42,150 +42,237 @@ msgstr "" "Rättigheten ”kör i bakgrund” kan ändras när som helst från " "programinställningarna." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Tillåt inte" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Tillåt" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Aktivera mikrofon?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Tillåt %s att använda kameran?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"Tillgång till din mikrofon kan ändras när som helst från " -"sekretessinställningarna." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s vill komma åt kameraenheter." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Ett program vill använda din mikrofon." +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Tillåt program att använda kameran?" -#: src/device.c:123 +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Ett program vill komma åt kameraenheter." + +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s vill använda din mikrofon." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Desktop-filens ID saknar .desktop-ändelse: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Aktivera högtalarna?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "Desktop-filens ID är ogiltigt" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." -msgstr "" -"Tillgång till dina högtalare kan ändras när som helst från " -"sekretessinställningarna." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "Desktop-post som ges till Install() är inte en giltig nyckelfil" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Ett program vill spela upp ljud." +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "Desktop-post som ges till Install() får endast ha en grupp" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s vill spela ett ljud." +msgid "Token given is invalid: %s" +msgstr "Angiven token är ogiltig: %s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Aktivera kamera?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "Desktop-post som ges till Install() är ogiltig" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "" -"Tillgång till din kamera kan ändras när som helst från " -"sekretessinställningarna." +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Desktop-filen överskrider maximal storlek (%d): %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Ett program vill använda din kamera." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "Angiven URL är ogiltig: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Ogiltig startartyp: %x" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "%s vill använda din kamera." +msgid "Unsupported launcher type: %x" +msgstr "Startartyp som inte stöds: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Dynamisk startarikon kunde inte valideras" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "RequestInstallToken() inte tillåtet för program-ID %s" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Desktop-filen ”%s” har ikon på okänd sökväg" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Desktop-filen ”%s” har ikon som inte kunde serialiseras" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Ingen dynamisk startare finns med ID ”%s”" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "Misslyckades med att skapa GDesktopAppInfo för startare med ID ”%s”" + +#: src/location.c:544 msgid "Deny Access" msgstr "Neka åtkomst" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Bevilja åtkomst" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Ge %s åtkomst till din plats?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s vill använda din plats." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Bevilja åtkomst till din plats?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "Ett program vill använda din plats." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "Platsåtkomst kan ändras när som helst från sekretessinställningarna." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Neka" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Tillåt %s att ta skärmbilder?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s vill kunna ta skärmbilder när som helst." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Tillåt program att ta skärmbilder?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Ett program vill kunna ta skärmbilder när som helst." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Denna rättighet kan ändras när som helst från sekretessinställningarna." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Begärd inställning hittades inte" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Tillåt inte" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Tillåt %s att ställa in bakgrunder?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s begär att kunna ändra bakgrundsbilden." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Tillåt program att ställa in bakgrunder?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Ett program begär att kunna ändra bakgrundsbilden." + +#, c-format +#~ msgid "Allow %s to Use the Microphone?" +#~ msgstr "Tillåt %s att använda mikrofonen?" + +#, c-format +#~ msgid "%s wants to access recording devices." +#~ msgstr "%s vill komma åt inspelningsenheter." + +#~ msgid "Allow app to Use the Microphone?" +#~ msgstr "Tillåt program att använda mikrofonen?" + +#~ msgid "An app wants to access recording devices." +#~ msgstr "Ett program vill komma åt inspelningsenheter." + +#, c-format +#~ msgid "Allow %s to Use the Speakers?" +#~ msgstr "Tillåt %s att använda högtalarna?" + +#, c-format +#~ msgid "%s wants to access audio devices." +#~ msgstr "%s vill komma åt ljudenheter." + +#~ msgid "Allow app to Use the Speakers?" +#~ msgstr "Tillåt program att använda högtalarna?" + +#~ msgid "An app wants to access audio devices." +#~ msgstr "Ett program vill komma åt ljudenheter." + +#, c-format +#~ msgid "Desktop file id missing app id prefix '%s.': %s" +#~ msgstr "Desktop-filens ID saknar program-ID-prefixet ”%s.”: %s" + +#, c-format +#~ msgid "Desktop entry given to Install() must not use --file-forwarding" +#~ msgstr "" +#~ "Desktop-post som ges till Install() får inte använda --file-forwarding" + +#, c-format +#~ msgid "DynamicLauncher install not supported for: %s" +#~ msgstr "DynamicLauncher-installation stöds inte för: %s" diff --git a/po/tr.po b/po/tr.po index a10d025..8380cae 100644 --- a/po/tr.po +++ b/po/tr.po @@ -1,194 +1,241 @@ # Turkish translation for xdg-desktop-portal. -# Copyright (C) 2017-2023 xdg-desktop-portal's COPYRIGHT HOLDER +# Copyright (C) 2017-2025 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. # # Muhammet Kara , 2017. # Serdar Sağlam , 2019. # Emin Tufan Çetin , 2019-2020. -# Sabri Ünal , 2020, 2023. +# Sabri Ünal , 2020, 2023, 2025. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2023-06-06 02:05+0300\n" -"Last-Translator: Berk Elyesa Yıldırım \n" -"Language-Team: Türkçe \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2025-02-09 21:34+0300\n" +"Last-Translator: Sabri Ünal \n" +"Language-Team: Türkçe \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Poedit 3.3.1\n" +"X-Generator: Poedit 3.5\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" -msgstr "%s, arka planda çalışmasına izin verilsin mi?" +msgstr "%s uygulamasına arka planda çalışma izin verilsin mi?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." -msgstr "%s, kendiliğinden başlamayı ve arka planda çalışmayı istiyor." +msgstr "%s kendiliğinden başlatılmayı ve arka planda çalışmayı istiyor." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." -msgstr "%s, arka planda çalışmak istiyor." +msgstr "%s arka planda çalışmak istiyor." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." msgstr "" -"‘Arka planda çalış’ ayarı, uygulama ayarlarından istediğiniz zaman " +"‘Arka planda çalış’ izni, uygulama ayarlarından istediğiniz zaman " "değiştirilebilir." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" -msgstr "İzin verme" +msgstr "İzin Verme" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" -msgstr "İzin ver" +msgstr "İzin Ver" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Mikrofon Açılsın Mı?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "%s Uygulamasının Kamerayı Kullanmasına İzin Verilsin Mi?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"Mikrofonunuza erişimi, istediğiniz zaman gizlilik ayarlarından " -"değiştirebilirsiniz." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s kamera aygıtlarına erişmek istiyor." -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Bir uygulama, mikrofonunuzu kullanmak istiyor." +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Uygulamanın Kamerayı Kullanmasına İzin Verilsin Mi?" -#: src/device.c:123 +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Bir uygulama kamera aygıtına erişmek istiyor." + +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s, mikrofonunuzu kullanmak istiyor." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Desktop dosya kimliğinin .desktop soneki eksik: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Hoparlörler Açılsın Mı?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "Desktop dosya kimliği geçersiz" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "Install() işlevine verilen desktop girdisi anahtar dosyası geçersiz" + +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" msgstr "" -"Hoparlörlerinize erişimi, istediğiniz zaman gizlilik ayarlarından " -"değiştirebilirsiniz." +"Install() işlevine verilen desktop girdisi yalnızca bir gruba sahip olmalıdır" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Bir uygulama, ses çalmak istiyor." +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "Verilen jeton geçersiz: %s" -#: src/device.c:136 +#: src/dynamic-launcher.c:400 #, c-format -msgid "%s wants to play sound." -msgstr "%s, ses çalmak istiyor." +msgid "Desktop entry given to Install() not valid" +msgstr "Install() işlevine verilen desktop girdisi geçersiz" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Kamera Açılsın Mı?" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Desktop dosyası azami boyutu (%d) aşıyor : %s" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "Verilen URL geçersiz: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Geçersiz başlatıcı türü: %x" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "Desteklenmeyen başlatıcı türü: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Dinamik başlatıcı simgesi doğrulanamadı" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" msgstr "" -"Kameranıza erişimi, istediğiniz zaman gizlilik ayarlarından " -"değiştirebilirsiniz." +"RequestInstallToken() işlevine %s uygulama kimliği için izin verilmiyor" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Tanınmayan yolda desktop dosyası '%s' simgesi" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Bir uygulama, kameranızı kullanmak istiyor." +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "Desktop dosyası '%s' simgesi serileştirilemedi" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "'%s' kimliğine sahip dinamik başlatıcı yok" -#: src/device.c:149 +#: src/dynamic-launcher.c:1001 #, c-format -msgid "%s wants to use your camera." -msgstr "%s, kameranızı kullanmak istiyor." +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "'%s' kimliğine sahip başlatıcı için GDesktopAppInfo oluşturulamadı" -#: src/location.c:527 +#: src/location.c:544 msgid "Deny Access" msgstr "Erişimi Reddet" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Erişim İzni Ver" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" -msgstr "%s, Bulunduğunuz Konuma Erişsin Mi?" +msgstr "%s, Konumunuza Erişsin Mi?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." -msgstr "%s, konumunuzu kullanmak istiyor." +msgstr "%s konumunuzu kullanmak istiyor." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" -msgstr "Konumunuza Erişim Verilsin Mi?" +msgstr "Konumunuza Erişim İzni Verilsin Mi?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." -msgstr "Bir uygulama, konumunuzu kullanmak istiyor." +msgstr "Bir uygulama konumunuzu kullanmak istiyor." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Konum erişimini, gizlilik ayarlarından istediğiniz zaman değiştirilebilir." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Reddet" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" -msgstr "%s, Ekran Görüntüsü Almasına İzin Verilsin Mi?" +msgstr "%s Uygulamasının Ekran Görüntüsü Almasına İzin Verilsin Mi?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s istediği zaman ekran görüntüsü alabilmek istiyor." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Uygulamaların Ekran Görüntüsü Almasına İzin Verilsin Mi?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Bir uygulama istediği zaman ekran görüntüsü alabilmek istiyor." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "Bu izin, gizlilik ayarlarından istediğiniz zaman değiştirilebilir." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "İstenen ayar bulunamadı" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "Aygıt kullanılır değil" + +#: src/usb.c:1342 +msgid "Not allowed" +msgstr "İzin verilmedi" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" -msgstr "%s, Arka Planı Belirlemesine İzin Verilsin Mi?" +msgstr "%s Uygulamasının Arka Planı Belirlemesine İzin Verilsin Mi?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." -msgstr "%s, arka planı değiştirme yetkisi istiyor." +msgstr "%s arka plan görüntüsünü değiştirebilmek istiyor." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Uygulamaların Arka Planı Belirlemesine İzin Verilsin Mi?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." -msgstr "Bir uygulama arka planı değiştirme yetkisi istiyor." +msgstr "Bir uygulama arka plan görüntüsünü değiştirebilmek istiyor." diff --git a/po/uk.po b/po/uk.po index 24333ee..6e7bb32 100644 --- a/po/uk.po +++ b/po/uk.po @@ -2,13 +2,13 @@ # Copyright (C) 2016 xdg-desktop-portal's COPYRIGHT HOLDER # This file is distributed under the same license as the xdg-desktop-portal package. # -# Yuri Chornoivan , 2016, 2018, 2019, 2022. +# Yuri Chornoivan , 2016, 2018, 2019, 2022, 2024. msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2022-10-04 23:10+0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-08-06 09:55+0300\n" "Last-Translator: Yuri Chornoivan \n" "Language-Team: Ukrainian \n" "Language: uk\n" @@ -17,24 +17,24 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : " "n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Lokalize 20.12.0\n" +"X-Generator: Lokalize 23.04.3\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "Дозволити запуск %s у фоновому режимі?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s надіслано запит щодо автоматичного запуску у фоновому режимі." -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s надіслано запит щодо запуску у фоновому режимі." -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." @@ -42,155 +42,214 @@ msgstr "" "Параметри запуску у фоновому режимі може бути будь-коли змінено за допомогою " "налаштовування параметрів програми." -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "Не дозволяти" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "Дозволити" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "Увімкнути мікрофон?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "Дозволити %s використання камери?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "" -"Параметри доступу до мікрофона може бути будь-коли змінено за допомогою " -"налаштувань конфіденційності." +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s потрібен доступ до пристроїв камери." + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "Дозволити програмі використовувати камеру?" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "Програмі потрібен доступ до використання мікрофона." +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "Програмі потрібен доступ до пристроїв камери." -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s потрібен доступ до використання мікрофона." +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "Ідентифікатор стільничного файла не містить суфікса .desktop: %s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "Увімкнути гучномовці?" +#: src/dynamic-launcher.c:133 +#, fuzzy, c-format +msgid "Desktop file id is not valid" +msgstr "Надано некоректний стільничний запис Install()" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" +"Стільничний запис, який надано Install(), не є коректним ключовим файлом" + +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" msgstr "" -"Параметри доступу до гучномовців може бути будь-коли змінено за допомогою " -"налаштувань конфіденційності." +"Стільничний запис, який надано Install(), повинен належати лише до однієї " +"групи" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "Програмі потрібно відтворити звукові дані." +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "Наданий жетон є некоректним: %s" -#: src/device.c:136 +#: src/dynamic-launcher.c:400 #, c-format -msgid "%s wants to play sound." -msgstr "%s потрібно відтворити звукові дані." +msgid "Desktop entry given to Install() not valid" +msgstr "Надано некоректний стільничний запис Install()" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "Увімкнути камеру?" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "Перевищено граничний розмір стільничного файла (%d): %s" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "Надана адреса є некоректною: %s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "Некоректний тип засобу запуску: %x" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "Непідтримуваний тип засобу запуску: %x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "Піктограма динамічного засобу запуску не пройшла перевірки" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" msgstr "" -"Параметри доступу до камери може бути будь-коли змінено за допомогою " -"налаштувань конфіденційності." +"RequestInstallToken() не можна використовувати для ідентифікатора програми %s" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "Програмі потрібен доступ до використання відеокамери." +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "Піктограма стільничного файла «%s» у невідомому каталозі" -#: src/device.c:149 +#: src/dynamic-launcher.c:943 #, c-format -msgid "%s wants to use your camera." -msgstr "%s потрібен доступ до використання відеокамери." +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" +"Не вдалося перетворити у послідовну форму піктограму у стільничному файлі " +"«%s»" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "Немає динамічного засобу запуску з ідентифікатором «%s»" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" +"Не вдалося створити GDesktopAppInfo для засобу запуску з ідентифікатором «%s»" -#: src/location.c:527 +#: src/location.c:544 msgid "Deny Access" msgstr "Заборонити доступ" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "Надати доступ" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "Надати %s доступ до даних щодо вашого перебування?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "" "%s потрібен доступ до використання даних щодо вашого місця перебування." -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "Надати доступ до даних щодо вашого перебування?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "" "Програмі потрібен доступ до використання даних щодо вашого місця перебування." -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "" "Параметри доступу до даних щодо вашого місця перебування може бути будь-коли " "змінено за допомогою налаштувань конфіденційності." -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "Заборонити" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "Дозволити %s робити знімки вікон?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s потрібна можливість будь-коли створювати знімки вікон." -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "Дозволити програмам робити знімки вікон?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "Програмі потрібна можливість будь-коли створювати знімки вікон." -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "" "Параметри цього доступу може бути будь-коли змінено за допомогою налаштувань " "конфіденційності." -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "Потрібного вам параметра не знайдено" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "Не дозволяти" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "Дозволити %s встановлювати фонове зображення?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s надіслано запит щодо доступу до зміни фонового зображення." -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "Дозволити програмам встановлювати фонове зображення?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "Програмою надіслано запит щодо доступу до зміни фонового зображення." + +#, c-format +#~ msgid "Desktop file id missing app id prefix '%s.': %s" +#~ msgstr "" +#~ "В ідентифікаторі стільничного файла пропущено префікс ідентифікатора " +#~ "програми «%s.»: %s" diff --git a/po/xdg-desktop-portal.pot b/po/xdg-desktop-portal.pot new file mode 100644 index 0000000..9ba574a --- /dev/null +++ b/po/xdg-desktop-portal.pot @@ -0,0 +1,231 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the xdg-desktop-portal package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: xdg-desktop-portal\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/background.c:827 +#, c-format +msgid "Allow %s to run in the background?" +msgstr "" + +#: src/background.c:831 +#, c-format +msgid "%s requests to be started automatically and run in the background." +msgstr "" + +#: src/background.c:833 +#, c-format +msgid "%s requests to run in the background." +msgstr "" + +#: src/background.c:834 +msgid "" +"The ‘run in background’ permission can be changed at any time from the " +"application settings." +msgstr "" + +#: src/background.c:838 +msgid "Don't allow" +msgstr "" + +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 +msgid "Allow" +msgstr "" + +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "" + +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "" + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "" + +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "" + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" + +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "" + +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" + +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" + +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" + +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "" + +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 +msgid "Deny Access" +msgstr "" + +#: src/location.c:546 +msgid "Grant Access" +msgstr "" + +#: src/location.c:566 +#, c-format +msgid "Give %s Access to Your Location?" +msgstr "" + +#: src/location.c:571 +#, c-format +msgid "%s wants to use your location." +msgstr "" + +#: src/location.c:580 +msgid "Grant Access to Your Location?" +msgstr "" + +#: src/location.c:581 +msgid "An application wants to use your location." +msgstr "" + +#: src/location.c:584 +msgid "Location access can be changed at any time from the privacy settings." +msgstr "" + +#: src/screenshot.c:245 src/wallpaper.c:185 +msgid "Deny" +msgstr "" + +#: src/screenshot.c:267 +#, c-format +msgid "Allow %s to Take Screenshots?" +msgstr "" + +#: src/screenshot.c:268 +#, c-format +msgid "%s wants to be able to take screenshots at any time." +msgstr "" + +#: src/screenshot.c:276 +msgid "Allow Applications to Take Screenshots?" +msgstr "" + +#: src/screenshot.c:277 +msgid "An application wants to be able to take screenshots at any time." +msgstr "" + +#: src/screenshot.c:280 src/wallpaper.c:220 +msgid "This permission can be changed at any time from the privacy settings." +msgstr "" + +#: src/settings.c:187 src/settings.c:223 +msgid "Requested setting not found" +msgstr "" + +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +msgid "Not allowed" +msgstr "" + +#: src/wallpaper.c:207 +#, c-format +msgid "Allow %s to Set Backgrounds?" +msgstr "" + +#: src/wallpaper.c:208 +#, c-format +msgid "%s is requesting to be able to change the background image." +msgstr "" + +#: src/wallpaper.c:217 +msgid "Allow Applications to Set Backgrounds?" +msgstr "" + +#: src/wallpaper.c:218 +msgid "An application is requesting to be able to change the background image." +msgstr "" diff --git a/po/zh_CN.po b/po/zh_CN.po index dda3fa7..6be1d43 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -4,181 +4,311 @@ # Mingye Wang , 2016. # 王滋涵 Zephyr Waitzman , 2019. # Dingzhong Chen , 2020. -# lumingzh , 2020-2023. +# lumingzh , 2020-2024. # msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" -"PO-Revision-Date: 2023-08-17 19:32+0800\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" +"PO-Revision-Date: 2024-10-08 08:42+0800\n" "Last-Translator: lumingzh \n" -"Language-Team: Chinese - China \n" +"Language-Team: Chinese (China) \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Gtranslator 42.0\n" -"Plural-Forms: nplurals=1; plural=0\n" +"X-Generator: Gtranslator 47.0\n" +"Plural-Forms: nplurals=1; plural=0;\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "允许 %s 在后台运行吗?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "%s 请求自动启动并在后台运行。" -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "%s 请求在后台运行。" -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." msgstr "您可随时在应用设置中修改“后台运行”的权限。" -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "禁止" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "允许" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "要打开麦克风吗?" +#: src/camera.c:105 +#, c-format +msgid "Allow %s to Use the Camera?" +msgstr "允许 %s 使用摄像头吗?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "您可随时在隐私设置中修改麦克风的访问权限。" +#: src/camera.c:106 +#, c-format +msgid "%s wants to access camera devices." +msgstr "%s 想访问摄像设备。" + +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "允许应用使用摄像头吗?" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "有应用程序想要使用您的麦克风。" +#: src/camera.c:111 +msgid "An app wants to access camera devices." +msgstr "有应用想访问摄像设备。" -#: src/device.c:123 +#: src/dynamic-launcher.c:122 #, c-format -msgid "%s wants to use your microphone." -msgstr "%s 想使用您的麦克风。" +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "桌面文件标识缺少 .desktop 后缀:%s" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "要打开扬声器吗?" +#: src/dynamic-launcher.c:133 +#, c-format +msgid "Desktop file id is not valid" +msgstr "桌面文件标识无效" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." -msgstr "您可随时在隐私设置中修改扬声器的访问权限。" +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "传递给 Install() 的桌面入口文件不是有效的设定键文件" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "有应用程序想要播放声音。" +#: src/dynamic-launcher.c:319 +#, fuzzy, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "传递给 Install() 的桌面入口文件只能有一个分组" -#: src/device.c:136 +#: src/dynamic-launcher.c:356 #, c-format -msgid "%s wants to play sound." -msgstr "%s 想播放声音。" +msgid "Token given is invalid: %s" +msgstr "提供的令牌无效:%s" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "要打开摄像头吗?" +#: src/dynamic-launcher.c:400 +#, c-format +msgid "Desktop entry given to Install() not valid" +msgstr "传递给 Install() 的桌面入口文件无效" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "您可随时在隐私设置中修改摄像头的访问权限。" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "桌面文件超出最大尺寸(%d):%s" + +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "提供的 URL 无效:%s" + +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "无效的启动器类型:%x" + +#: src/dynamic-launcher.c:593 +#, c-format +msgid "Unsupported launcher type: %x" +msgstr "不支持的启动器类型:%x" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "动态启动器图标验证失败" + +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "应用标识 %s 不允许使用 RequestInstallToken()" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "有应用程序想要使用您的摄像头。" +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "桌面文件“%s”图标位于未识别的路径" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "桌面文件“%s”图标序列化失败" -#: src/device.c:149 +#: src/dynamic-launcher.c:982 #, c-format -msgid "%s wants to use your camera." -msgstr "%s 想使用您的摄像头。" +msgid "No dynamic launcher exists with id '%s'" +msgstr "带有标识“%s”的动态启动器不存在" -#: src/location.c:527 +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "为带有标识“%s”的启动器创建 GDesktopAppInfo 失败" + +#: src/location.c:544 msgid "Deny Access" msgstr "拒绝访问" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "授权访问" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "授予 %s 访问您的位置信息权限?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "%s 想使用您的位置信息。" -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "授权访问您的位置信息?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "有应用程序想使用您的位置信息。" -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "您可随时在隐私设置中修改位置信息的访问权限。" -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "拒绝" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, c-format msgid "Allow %s to Take Screenshots?" msgstr "允许 %s 获取截屏吗?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "%s 想要在任何时候都能获取截屏。" -#: src/screenshot.c:265 +#: src/screenshot.c:276 msgid "Allow Applications to Take Screenshots?" msgstr "允许应用程序获取截屏吗?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 msgid "An application wants to be able to take screenshots at any time." msgstr "有应用程序想要在任何时候都能获取截屏。" -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "您可随时在隐私设置中修改该权限。" -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "未找到请求的设置" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "禁止" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "允许 %s 设置背景吗?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "%s 正在请求允许修改背景图像。" -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "允许应用程序设置背景吗?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "一个应用程序正在请求允许修改背景图像。" + +#, c-format +#~ msgid "Allow %s to Use the Microphone?" +#~ msgstr "允许 %s 使用麦克风吗?" + +#, c-format +#~ msgid "%s wants to access recording devices." +#~ msgstr "%s 想访问录音设备。" + +#~ msgid "Allow app to Use the Microphone?" +#~ msgstr "允许应用使用麦克风吗?" + +#~ msgid "An app wants to access recording devices." +#~ msgstr "有应用想访问录音设备。" + +#, c-format +#~ msgid "Allow %s to Use the Speakers?" +#~ msgstr "允许 %s 使用扬声器吗?" + +#, c-format +#~ msgid "%s wants to access audio devices." +#~ msgstr "%s 想访问音频设备。" + +#~ msgid "Allow app to Use the Speakers?" +#~ msgstr "允许应用使用扬声器吗?" + +#~ msgid "An app wants to access audio devices." +#~ msgstr "有应用想访问音频设备。" + +#, c-format +#~ msgid "Desktop file id missing app id prefix '%s.': %s" +#~ msgstr "桌面文件标识缺少应用标识前缀“%s”:%s" + +#, c-format +#~ msgid "Desktop entry given to Install() must not use --file-forwarding" +#~ msgstr "传递给 Install() 的桌面入口文件不能使用 --file-forwarding" + +#, c-format +#~ msgid "DynamicLauncher install not supported for: %s" +#~ msgstr "不支持动态启动器安装:%s" + +#~ msgid "Turn On Microphone?" +#~ msgstr "要打开麦克风吗?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "您可随时在隐私设置中修改麦克风的访问权限。" + +#~ msgid "An application wants to use your microphone." +#~ msgstr "有应用程序想要使用您的麦克风。" + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "%s 想使用您的麦克风。" + +#~ msgid "Turn On Speakers?" +#~ msgstr "要打开扬声器吗?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "您可随时在隐私设置中修改扬声器的访问权限。" + +#~ msgid "An application wants to play sound." +#~ msgstr "有应用程序想要播放声音。" + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "%s 想播放声音。" + +#~ msgid "Turn On Camera?" +#~ msgstr "要打开摄像头吗?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "您可随时在隐私设置中修改摄像头的访问权限。" diff --git a/po/zh_TW.po b/po/zh_TW.po index 896e0ac..bafa5db 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: xdg-desktop-portal master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-18 14:00-0300\n" +"POT-Creation-Date: 2025-04-23 10:29-0300\n" "PO-Revision-Date: 2022-07-03 04:00+0800\n" "Last-Translator: Freddy Cheng \n" "Language-Team: Chinese (Taiwan) \n" @@ -17,166 +17,257 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.0.1\n" -#: src/background.c:747 +#: src/background.c:827 #, c-format msgid "Allow %s to run in the background?" msgstr "允許《%s》於背景執行嗎?" -#: src/background.c:751 +#: src/background.c:831 #, c-format msgid "%s requests to be started automatically and run in the background." msgstr "《%s》請求自動啟動並於背景執行。" -#: src/background.c:753 +#: src/background.c:833 #, c-format msgid "%s requests to run in the background." msgstr "《%s》請求於背景執行。" -#: src/background.c:754 +#: src/background.c:834 msgid "" "The ‘run in background’ permission can be changed at any time from the " "application settings." msgstr "背景執行許可權可隨時從應用程式設定中更改。" -#: src/background.c:759 +#: src/background.c:838 msgid "Don't allow" msgstr "不允許" -#: src/background.c:760 src/screenshot.c:238 src/wallpaper.c:183 +#: src/background.c:839 src/screenshot.c:247 src/wallpaper.c:187 msgid "Allow" msgstr "允許" -#: src/device.c:116 -msgid "Turn On Microphone?" -msgstr "開啟麥克風?" +#: src/camera.c:105 +#, fuzzy, c-format +msgid "Allow %s to Use the Camera?" +msgstr "允許《%s》於背景執行嗎?" -#: src/device.c:117 -msgid "" -"Access to your microphone can be changed at any time from the privacy " -"settings." -msgstr "麥克風取用權可隨時從隱私設定中更改。" +#: src/camera.c:106 +#, fuzzy, c-format +msgid "%s wants to access camera devices." +msgstr "《%s》想要使用您的相機。" -#: src/device.c:121 -msgid "An application wants to use your microphone." -msgstr "應用程式想要使用您的麥克風。" +#: src/camera.c:110 +msgid "Allow app to Use the Camera?" +msgstr "" -#: src/device.c:123 +#: src/camera.c:111 +#, fuzzy +msgid "An app wants to access camera devices." +msgstr "應用程式想要使用您的相機。" + +#: src/dynamic-launcher.c:122 +#, c-format +msgid "Desktop file id missing .desktop suffix: %s" +msgstr "" + +#: src/dynamic-launcher.c:133 #, c-format -msgid "%s wants to use your microphone." -msgstr "《%s》想要使用您的麥克風。" +msgid "Desktop file id is not valid" +msgstr "" -#: src/device.c:129 -msgid "Turn On Speakers?" -msgstr "開啟喇叭?" +#: src/dynamic-launcher.c:306 +#, c-format +msgid "Desktop entry given to Install() not a valid key file" +msgstr "" -#: src/device.c:130 -msgid "" -"Access to your speakers can be changed at any time from the privacy settings." -msgstr "喇叭取用權可隨時從隱私設定中更改。" +#: src/dynamic-launcher.c:319 +#, c-format +msgid "Desktop entry given to Install() must have exactly one group" +msgstr "" -#: src/device.c:134 -msgid "An application wants to play sound." -msgstr "應用程式想要播放聲音。" +#: src/dynamic-launcher.c:356 +#, c-format +msgid "Token given is invalid: %s" +msgstr "" -#: src/device.c:136 +#: src/dynamic-launcher.c:400 #, c-format -msgid "%s wants to play sound." -msgstr "《%s》想要播放聲音。" +msgid "Desktop entry given to Install() not valid" +msgstr "" -#: src/device.c:142 -msgid "Turn On Camera?" -msgstr "開啟相機?" +#: src/dynamic-launcher.c:409 src/dynamic-launcher.c:831 +#: src/dynamic-launcher.c:880 +#, c-format +msgid "Desktop file exceeds max size (%d): %s" +msgstr "" -#: src/device.c:143 -msgid "" -"Access to your camera can be changed at any time from the privacy settings." -msgstr "相機取用權可隨時從隱私設定中更改。" +#: src/dynamic-launcher.c:563 +#, c-format +msgid "URL given is invalid: %s" +msgstr "" -#: src/device.c:147 -msgid "An application wants to use your camera." -msgstr "應用程式想要使用您的相機。" +#: src/dynamic-launcher.c:586 +#, c-format +msgid "Invalid launcher type: %x" +msgstr "" -#: src/device.c:149 +#: src/dynamic-launcher.c:593 #, c-format -msgid "%s wants to use your camera." -msgstr "《%s》想要使用您的相機。" +msgid "Unsupported launcher type: %x" +msgstr "" + +#: src/dynamic-launcher.c:655 src/dynamic-launcher.c:725 +msgid "Dynamic launcher icon failed validation" +msgstr "" -#: src/location.c:527 +#: src/dynamic-launcher.c:741 +#, c-format +msgid "RequestInstallToken() not allowed for app id %s" +msgstr "" + +#: src/dynamic-launcher.c:920 +#, c-format +msgid "Desktop file '%s' icon at unrecognized path" +msgstr "" + +#: src/dynamic-launcher.c:943 +#, c-format +msgid "Desktop file '%s' icon failed to serialize" +msgstr "" + +#: src/dynamic-launcher.c:982 +#, c-format +msgid "No dynamic launcher exists with id '%s'" +msgstr "" + +#: src/dynamic-launcher.c:1001 +#, c-format +msgid "Failed to create GDesktopAppInfo for launcher with id '%s'" +msgstr "" + +#: src/location.c:544 msgid "Deny Access" msgstr "不允許" -#: src/location.c:529 +#: src/location.c:546 msgid "Grant Access" msgstr "允許" -#: src/location.c:551 +#: src/location.c:566 #, c-format msgid "Give %s Access to Your Location?" msgstr "允許《%s》取用您的位置資訊嗎?" -#: src/location.c:556 +#: src/location.c:571 #, c-format msgid "%s wants to use your location." msgstr "《%s》想使用您的位置資訊。" -#: src/location.c:565 +#: src/location.c:580 msgid "Grant Access to Your Location?" msgstr "允許取用您的位置資訊?" -#: src/location.c:566 +#: src/location.c:581 msgid "An application wants to use your location." msgstr "應用程式想要使用您的位置資訊。" -#: src/location.c:569 +#: src/location.c:584 msgid "Location access can be changed at any time from the privacy settings." msgstr "位置資訊取用權可隨時從隱私設定中更改。" -#: src/screenshot.c:236 src/wallpaper.c:181 +#: src/screenshot.c:245 src/wallpaper.c:185 msgid "Deny" msgstr "不允許" -#: src/screenshot.c:256 +#: src/screenshot.c:267 #, fuzzy, c-format msgid "Allow %s to Take Screenshots?" msgstr "允許《%s》設定背景嗎?" -#: src/screenshot.c:257 +#: src/screenshot.c:268 #, c-format msgid "%s wants to be able to take screenshots at any time." msgstr "" -#: src/screenshot.c:265 +#: src/screenshot.c:276 #, fuzzy msgid "Allow Applications to Take Screenshots?" msgstr "允許應用程式設定背景?" -#: src/screenshot.c:266 +#: src/screenshot.c:277 #, fuzzy msgid "An application wants to be able to take screenshots at any time." msgstr "應用程式想要使用您的麥克風。" -#: src/screenshot.c:269 src/wallpaper.c:218 +#: src/screenshot.c:280 src/wallpaper.c:220 msgid "This permission can be changed at any time from the privacy settings." msgstr "該許可權可隨時從隱私設定中更改。" -#: src/settings.c:127 src/settings.c:163 +#: src/settings.c:187 src/settings.c:223 msgid "Requested setting not found" msgstr "找不到請求的設定" -#: src/wallpaper.c:205 +#: src/usb.c:1328 +msgid "Device not available" +msgstr "" + +#: src/usb.c:1342 +#, fuzzy +msgid "Not allowed" +msgstr "不允許" + +#: src/wallpaper.c:207 #, c-format msgid "Allow %s to Set Backgrounds?" msgstr "允許《%s》設定背景嗎?" -#: src/wallpaper.c:206 +#: src/wallpaper.c:208 #, c-format msgid "%s is requesting to be able to change the background image." msgstr "《%s》請求能變更背景圖片的許可權。" -#: src/wallpaper.c:215 +#: src/wallpaper.c:217 msgid "Allow Applications to Set Backgrounds?" msgstr "允許應用程式設定背景?" -#: src/wallpaper.c:216 +#: src/wallpaper.c:218 msgid "An application is requesting to be able to change the background image." msgstr "應用程式請求能變更背景圖片的許可權。" + +#~ msgid "Turn On Microphone?" +#~ msgstr "開啟麥克風?" + +#~ msgid "" +#~ "Access to your microphone can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "麥克風取用權可隨時從隱私設定中更改。" + +#~ msgid "An application wants to use your microphone." +#~ msgstr "應用程式想要使用您的麥克風。" + +#, c-format +#~ msgid "%s wants to use your microphone." +#~ msgstr "《%s》想要使用您的麥克風。" + +#~ msgid "Turn On Speakers?" +#~ msgstr "開啟喇叭?" + +#~ msgid "" +#~ "Access to your speakers can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "喇叭取用權可隨時從隱私設定中更改。" + +#~ msgid "An application wants to play sound." +#~ msgstr "應用程式想要播放聲音。" + +#, c-format +#~ msgid "%s wants to play sound." +#~ msgstr "《%s》想要播放聲音。" + +#~ msgid "Turn On Camera?" +#~ msgstr "開啟相機?" + +#~ msgid "" +#~ "Access to your camera can be changed at any time from the privacy " +#~ "settings." +#~ msgstr "相機取用權可隨時從隱私設定中更改。" diff --git a/src/account.c b/src/account.c index 823eec2..aa64663 100644 --- a/src/account.c +++ b/src/account.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -32,8 +34,8 @@ #include #include "account.h" -#include "request.h" -#include "documents.h" +#include "xdp-request.h" +#include "xdp-documents.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -67,16 +69,15 @@ send_response_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = task_data; + XdpRequest *request = task_data; guint response; GVariant *results; - GVariantBuilder new_results; + g_auto(GVariantBuilder) new_results = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GVariant) idv = NULL; g_autoptr(GVariant) namev = NULL; const char *image; - g_variant_builder_init (&new_results, G_VARIANT_TYPE_VARDICT); - REQUEST_AUTOLOCK (request); response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "response")); @@ -99,7 +100,7 @@ send_response_in_thread_func (GTask *task, if (xdp_app_info_is_host (request->app_info)) ruri = g_strdup (image); else - ruri = register_document (image, xdp_app_info_get_id (request->app_info), DOCUMENT_FLAG_NONE, &error); + ruri = xdp_register_document (image, xdp_app_info_get_id (request->app_info), XDP_DOCUMENT_FLAG_NONE, &error); if (ruri == NULL) g_warning ("Failed to register %s: %s", image, error->message); @@ -116,7 +117,7 @@ send_response_in_thread_func (GTask *task, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&new_results)); - request_unexport (request); + xdp_request_unexport (request); } } @@ -125,7 +126,7 @@ get_user_information_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) results = NULL; g_autoptr(GError) error = NULL; @@ -178,11 +179,12 @@ handle_get_user_information (XdpDbusAccount *object, const gchar *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options; + g_auto(GVariantBuilder) options = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_debug ("Handling GetUserInformation"); @@ -199,10 +201,9 @@ handle_get_user_information (XdpDbusAccount *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); xdp_filter_options (arg_options, &options, user_information_options, G_N_ELEMENTS (user_information_options), NULL); diff --git a/src/account.h b/src/account.h index 6e466f2..8652e71 100644 --- a/src/account.h +++ b/src/account.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/background.c b/src/background.c index 2156776..58776e1 100644 --- a/src/background.c +++ b/src/background.c @@ -1,10 +1,12 @@ /* * Copyright © 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -25,15 +27,16 @@ #include #include -#include "call.h" +#include "xdp-call.h" #include "background.h" -#include "background-monitor.h" -#include "request.h" -#include "permissions.h" +#include "xdp-background-monitor.h" +#include "flatpak-instance.h" +#include "xdp-permissions.h" +#include "xdp-request.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" +#include "xdp-app-info.h" #include "xdp-utils.h" -#include "flatpak-instance.h" /* Implementation notes: * @@ -72,7 +75,7 @@ struct _Background { XdpDbusBackgroundSkeleton parent_instance; - BackgroundMonitor *monitor; + XdpBackgroundMonitor *monitor; }; struct _BackgroundClass @@ -105,11 +108,6 @@ typedef enum { IGNORE = 2 } NotifyResult; -typedef enum { - AUTOSTART_FLAGS_NONE = 0, - AUTOSTART_FLAGS_ACTIVATABLE = 1 << 0, -} AutostartFlags; - static GVariant * get_all_permissions (void) { @@ -117,7 +115,7 @@ get_all_permissions (void) g_autoptr(GVariant) out_perms = NULL; g_autoptr(GVariant) out_data = NULL; - if (!xdp_dbus_impl_permission_store_call_lookup_sync (get_permission_store (), + if (!xdp_dbus_impl_permission_store_call_lookup_sync (xdp_get_permission_store (), PERMISSION_TABLE, PERMISSION_ID, &out_perms, @@ -133,7 +131,7 @@ get_all_permissions (void) return g_steal_pointer (&out_perms); } -static Permission +static XdpPermission get_one_permission (const char *app_id, GVariant *perms) { @@ -143,39 +141,39 @@ get_one_permission (const char *app_id, { g_debug ("No background permissions found"); - return PERMISSION_UNSET; + return XDP_PERMISSION_UNSET; } else if (!g_variant_lookup (perms, app_id, "^a&s", &permissions)) { g_debug ("No background permissions stored for: app %s", app_id); - return PERMISSION_UNSET; + return XDP_PERMISSION_UNSET; } else if (g_strv_length ((char **)permissions) != 1) { g_autofree char *a = g_strjoinv (" ", (char **)permissions); g_warning ("Wrong background permission format, ignoring (%s)", a); - return PERMISSION_UNSET; + return XDP_PERMISSION_UNSET; } g_debug ("permission store: background, app %s -> %s", app_id, permissions[0]); if (strcmp (permissions[0], "yes") == 0) - return PERMISSION_YES; + return XDP_PERMISSION_YES; else if (strcmp (permissions[0], "no") == 0) - return PERMISSION_NO; + return XDP_PERMISSION_NO; else if (strcmp (permissions[0], "ask") == 0) - return PERMISSION_ASK; + return XDP_PERMISSION_ASK; else { g_autofree char *a = g_strjoinv (" ", (char **)permissions); g_warning ("Wrong permission format, ignoring (%s)", a); } - return PERMISSION_UNSET; + return XDP_PERMISSION_UNSET; } -static Permission +static XdpPermission get_permission (const char *app_id) { g_autoptr(GVariant) perms = NULL; @@ -184,21 +182,21 @@ get_permission (const char *app_id) if (perms) return get_one_permission (app_id, perms); - return PERMISSION_UNSET; + return XDP_PERMISSION_UNSET; } static void set_permission (const char *app_id, - Permission permission) + XdpPermission permission) { g_autoptr(GError) error = NULL; const char *permissions[2]; - if (permission == PERMISSION_ASK) + if (permission == XDP_PERMISSION_ASK) permissions[0] = "ask"; - else if (permission == PERMISSION_YES) + else if (permission == XDP_PERMISSION_YES) permissions[0] = "yes"; - else if (permission == PERMISSION_NO) + else if (permission == XDP_PERMISSION_NO) permissions[0] = "no"; else { @@ -207,7 +205,7 @@ set_permission (const char *app_id, } permissions[1] = NULL; - if (!xdp_dbus_impl_permission_store_call_set_permission_sync (get_permission_store (), + if (!xdp_dbus_impl_permission_store_call_set_permission_sync (xdp_get_permission_store (), PERMISSION_TABLE, TRUE, PERMISSION_ID, @@ -284,7 +282,7 @@ typedef struct { AppState state; char *handle; gboolean notified; - Permission permission; + XdpPermission permission; char *status_message; } InstanceData; @@ -354,18 +352,18 @@ remove_outdated_instances (int stamp) static void update_background_monitor_properties (void) { - GVariantBuilder builder; + g_auto(GVariantBuilder) builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("aa{sv}")); GHashTableIter iter; InstanceData *data; char *id; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}")); - G_LOCK (applications); g_hash_table_iter_init (&iter, applications); while (g_hash_table_iter_next (&iter, (gpointer *)&id, (gpointer *)&data)) { - GVariantBuilder app_builder; + g_auto(GVariantBuilder) app_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); const char *app_id; const char *id; @@ -379,7 +377,6 @@ update_background_monitor_properties (void) app_id = flatpak_instance_get_app (data->instance); g_assert (app_id != NULL); - g_variant_builder_init (&app_builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&app_builder, "{sv}", "app_id", g_variant_new_string (app_id)); g_variant_builder_add (&app_builder, "{sv}", "instance", g_variant_new_string (id)); if (data->status_message) @@ -417,7 +414,7 @@ typedef struct { char *app_id; char *id; char *name; - Permission perm; + XdpPermission perm; pid_t child_pid; } NotificationData; @@ -464,15 +461,15 @@ notify_background_done (GObject *source, { g_debug ("Allowing app %s to run in background", nd->app_id); - if (nd->perm != PERMISSION_ASK) - nd->perm = PERMISSION_YES; + if (nd->perm != XDP_PERMISSION_ASK) + nd->perm = XDP_PERMISSION_YES; } else if (result == FORBID) { g_debug ("Forbid app %s to run in background", nd->app_id); - if (nd->perm != PERMISSION_ASK) - nd->perm = PERMISSION_NO; + if (nd->perm != XDP_PERMISSION_ASK) + nd->perm = XDP_PERMISSION_NO; g_message ("Terminating app %s (process %d) because the app does not " "have permission to run in the background. You may be able to " @@ -489,7 +486,7 @@ notify_background_done (GObject *source, else g_debug ("Unexpected response from NotifyBackground: %u", result); - if (nd->perm != PERMISSION_UNSET) + if (nd->perm != XDP_PERMISSION_UNSET) set_permission (nd->app_id, nd->perm); G_LOCK (applications); @@ -581,15 +578,15 @@ check_background_apps (void) switch (idata->permission) { - case PERMISSION_NO: + case XDP_PERMISSION_NO: idata->stamp = 0; g_debug ("Kill app %s (pid %d)", app_id, child_pid); kill (child_pid, SIGKILL); break; - case PERMISSION_ASK: - case PERMISSION_UNSET: + case XDP_PERMISSION_ASK: + case XDP_PERMISSION_UNSET: { NotificationData *nd = g_new0 (NotificationData, 1); @@ -613,7 +610,7 @@ check_background_apps (void) } break; - case PERMISSION_YES: + case XDP_PERMISSION_YES: default: break; } @@ -689,25 +686,110 @@ instances_changed (gpointer data) g_main_context_wakeup (monitor_context); } +gboolean +enable_autostart_sync (XdpAppInfo *app_info, + gboolean enable, + const char * const *autostart_exec, + gboolean activatable, + gboolean *out_enabled, + GError **error) +{ + GStrv exec = (GStrv)autostart_exec; + g_autofree char *cmd = NULL; + g_autofree char *file = NULL; + g_autofree char *dir = NULL; + g_autofree char *path = NULL; + g_autoptr(GKeyFile) keyfile = NULL; + const char *appid = xdp_app_info_get_id (app_info); + + if (!appid) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Autostart not supported"); + return FALSE; + } + + file = g_strconcat (appid, ".desktop", NULL); + dir = g_build_filename (g_get_user_config_dir (), "autostart", NULL); + path = g_build_filename (dir, file, NULL); + + if (!enable) + { + unlink (path); + + *out_enabled = FALSE; + return TRUE; + } + + if (g_mkdir_with_parents (dir, 0755) != 0) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + "Could not create directory for Autostart files"); + return FALSE; + } + + keyfile = g_key_file_new (); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TYPE, + "Application"); + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_NAME, + appid); /* FIXME: The app id isn't the name */ + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + "X-XDP-Autostart", + appid); + + if (exec) + cmd = g_strjoinv (" ", exec); + + if (cmd) + { + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_EXEC, + cmd); + } + + if (activatable) + { + g_key_file_set_boolean (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE, + TRUE); + } + + if (!xdp_app_info_validate_autostart (app_info, keyfile, autostart_exec, + NULL, error)) + return FALSE; + + if (!g_key_file_save_to_file (keyfile, path, error)) + return FALSE; + + *out_enabled = TRUE; + return TRUE; +} + static void handle_request_background_in_thread_func (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; + XdpRequest *request = XDP_REQUEST (task_data); GVariant *options; const char *id; - Permission permission; + XdpPermission permission; const char *reason = NULL; gboolean autostart_requested = FALSE; gboolean autostart_enabled; gboolean allowed; g_autoptr(GError) error = NULL; - const char * const *autostart_exec = { NULL }; - AutostartFlags autostart_flags = AUTOSTART_FLAGS_NONE; + g_autofree const char **autostart_exec = { NULL }; gboolean activatable = FALSE; - g_auto(GStrv) commandline = NULL; REQUEST_AUTOLOCK (request); @@ -717,21 +799,19 @@ handle_request_background_in_thread_func (GTask *task, g_variant_lookup (options, "commandline", "^a&s", &autostart_exec); g_variant_lookup (options, "dbus-activatable", "b", &activatable); - if (activatable) - autostart_flags |= AUTOSTART_FLAGS_ACTIVATABLE; - id = xdp_app_info_get_id (request->app_info); if (xdp_app_info_is_host (request->app_info)) - permission = PERMISSION_YES; + permission = XDP_PERMISSION_YES; else permission = get_permission (id); g_debug ("Handle RequestBackground for '%s'", id); - if (permission == PERMISSION_ASK) + if (permission == XDP_PERMISSION_ASK) { - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autofree char *app_id = NULL; g_autofree char *title = NULL; g_autofree char *subtitle = NULL; @@ -739,9 +819,9 @@ handle_request_background_in_thread_func (GTask *task, guint32 response = 2; g_autoptr(GVariant) results = NULL; g_autoptr(GError) error = NULL; - g_autoptr(GAppInfo) info = NULL; + GAppInfo *info = NULL; - info = xdp_app_info_load_app_info (request->app_info); + info = xdp_app_info_get_gappinfo (request->app_info); app_id = info ? xdp_get_app_id_from_desktop_id (g_app_info_get_id (info)) : g_strdup (id); title = g_strdup_printf (_("Allow %s to run in the background?"), info ? g_app_info_get_display_name (info) : id); @@ -755,7 +835,6 @@ handle_request_background_in_thread_func (GTask *task, g_debug ("Calling backend for background access for: %s", id); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&opt_builder, "{sv}", "deny_label", g_variant_new_string (_("Don't allow"))); g_variant_builder_add (&opt_builder, "{sv}", "grant_label", g_variant_new_string (_("Allow"))); if (!xdp_dbus_impl_access_call_access_dialog_sync (access_impl, @@ -779,9 +858,9 @@ handle_request_background_in_thread_func (GTask *task, } else { - allowed = permission != PERMISSION_NO; - if (permission == PERMISSION_UNSET) - set_permission (id, PERMISSION_YES); + allowed = permission != XDP_PERMISSION_NO; + if (permission == XDP_PERMISSION_UNSET) + set_permission (id, XDP_PERMISSION_YES); } g_debug ("Setting autostart for %s to %s", id, @@ -789,20 +868,12 @@ handle_request_background_in_thread_func (GTask *task, autostart_enabled = FALSE; - commandline = xdp_app_info_rewrite_commandline (request->app_info, autostart_exec, - FALSE /* don't quote escape */); - if (commandline == NULL) - { - g_debug ("Autostart not supported for: %s", id); - } - else if (!xdp_dbus_impl_background_call_enable_autostart_sync (background_impl, - id, - allowed && autostart_requested, - (const char * const *)commandline, - autostart_flags, - &autostart_enabled, - NULL, - &error)) + if (!enable_autostart_sync (request->app_info, + allowed && autostart_requested, + autostart_exec, + activatable, + &autostart_enabled, + &error)) { g_warning ("EnableAutostart call failed: %s", error->message); g_clear_error (&error); @@ -811,9 +882,9 @@ handle_request_background_in_thread_func (GTask *task, if (request->exported) { XdgDesktopPortalResponseEnum portal_response; - GVariantBuilder results; + g_auto(GVariantBuilder) results = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&results, "{sv}", "background", g_variant_new_boolean (allowed)); g_variant_builder_add (&results, "{sv}", "autostart", g_variant_new_boolean (autostart_enabled)); @@ -825,7 +896,7 @@ handle_request_background_in_thread_func (GTask *task, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), portal_response, g_variant_builder_end (&results)); - request_unexport (request); + xdp_request_unexport (request); } } @@ -899,16 +970,16 @@ handle_request_background (XdpDbusBackground *object, const char *arg_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; g_autoptr(GTask) task = NULL; - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &opt_builder, background_options, G_N_ELEMENTS (background_options), &error)) @@ -934,8 +1005,8 @@ handle_request_background (XdpDbusBackground *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_background_complete_request_background (object, invocation, request->id); @@ -981,9 +1052,9 @@ handle_set_status_in_thread_func (GTask *task, InstanceData *data; const char *id = NULL; GVariant *options; - Call *call; + XdpCall *call; - call = call_from_invocation (invocation); + call = xdp_call_from_invocation (invocation); id = xdp_app_info_get_instance (call->app_info); options = g_object_get_data (G_OBJECT (invocation), "options"); @@ -1088,11 +1159,12 @@ handle_set_status (XdpDbusBackground *object, g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = NULL; - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); const char *id = NULL; - Call *call; + XdpCall *call; - call = call_from_invocation (invocation); + call = xdp_call_from_invocation (invocation); g_debug ("Handling SetStatus call from %s", xdp_app_info_get_id (call->app_info)); @@ -1115,7 +1187,6 @@ handle_set_status (XdpDbusBackground *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &opt_builder, set_status_options, G_N_ELEMENTS (set_status_options), @@ -1194,7 +1265,7 @@ background_create (GDBusConnection *connection, g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (background_impl), G_MAXINT); background = g_object_new (background_get_type (), NULL); - background->monitor = background_monitor_new (NULL, &error); + background->monitor = xdp_background_monitor_new (NULL, &error); if (background->monitor == NULL) { g_warning ("Failed to create background monitor: %s", error->message); diff --git a/src/background.h b/src/background.h index f66b208..7269220 100644 --- a/src/background.h +++ b/src/background.h @@ -1,10 +1,12 @@ /* * Copyright © 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/camera.c b/src/camera.c index c358f09..2bfcd4b 100644 --- a/src/camera.c +++ b/src/camera.c @@ -1,10 +1,12 @@ /* * Copyright © 2018-2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -23,15 +25,18 @@ #include #include -#include "device.h" -#include "request.h" -#include "permissions.h" +#include "xdp-request.h" +#include "xdp-permissions.h" #include "pipewire.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" +#define PERMISSION_TABLE "devices" +#define PERMISSION_DEVICE_CAMERA "camera" + static XdpDbusImplLockdown *lockdown; +static XdpDbusImplAccess *access_impl; typedef struct _Camera Camera; typedef struct _CameraClass CameraClass; @@ -66,45 +71,137 @@ static gboolean create_pipewire_remote (Camera *camera, GError **error); +static gboolean +query_permission_sync (XdpRequest *request) +{ + XdpPermission permission; + const char *app_id; + gboolean allowed; + + app_id = (const char *)g_object_get_data (G_OBJECT (request), "app-id"); + permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_DEVICE_CAMERA); + if (permission == XDP_PERMISSION_ASK || permission == XDP_PERMISSION_UNSET) + { + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autofree char *title = NULL; + g_autofree char *body = NULL; + guint32 response = 2; + g_autoptr(GVariant) results = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GAppInfo) info = NULL; + g_autoptr(XdpDbusImplRequest) impl_request = NULL; + + if (app_id[0] != 0) + { + g_autofree char *desktop_id = g_strconcat (app_id, ".desktop", NULL); + info = (GAppInfo*)g_desktop_app_info_new (desktop_id); + } + + g_variant_builder_add (&opt_builder, "{sv}", "icon", g_variant_new_string ("camera-web-symbolic")); + + if (info) + { + title = g_strdup_printf (_("Allow %s to Use the Camera?"), g_app_info_get_display_name (info)); + body = g_strdup_printf (_("%s wants to access camera devices."), g_app_info_get_display_name (info)); + } + else + { + title = g_strdup (_("Allow app to Use the Camera?")); + body = g_strdup (_("An app wants to access camera devices.")); + } + + impl_request = xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (access_impl)), + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + g_dbus_proxy_get_name (G_DBUS_PROXY (access_impl)), + request->id, + NULL, &error); + if (!impl_request) + return FALSE; + + xdp_request_set_impl_request (request, impl_request); + + g_debug ("Calling backend for device access to camera"); + + if (!xdp_dbus_impl_access_call_access_dialog_sync (access_impl, + request->id, + app_id, + "", + title, + "", + body, + g_variant_builder_end (&opt_builder), + &response, + &results, + NULL, + &error)) + { + g_warning ("A backend call failed: %s", error->message); + /* We do not want to set the permission if there was an error and the + * permission was UNSET. Setting a permission should only be done from + * user input. */ + return FALSE; + } + + allowed = response == 0; + + if (permission == XDP_PERMISSION_UNSET) + { + xdp_set_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_DEVICE_CAMERA, + allowed ? XDP_PERMISSION_YES : XDP_PERMISSION_NO); + } + } + else + allowed = permission == XDP_PERMISSION_YES ? TRUE : FALSE; + + return allowed; +} + static void handle_access_camera_in_thread_func (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; - const char *app_id; + XdpRequest *request = XDP_REQUEST (task_data); gboolean allowed; - app_id = (const char *)g_object_get_data (G_OBJECT (request), "app-id"); - - allowed = device_query_permission_sync (app_id, "camera", request); + allowed = query_permission_sync (request); REQUEST_AUTOLOCK (request); if (request->exported) { - GVariantBuilder results; + g_auto(GVariantBuilder) results = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); guint32 response; - g_variant_builder_init (&results, G_VARIANT_TYPE_VARDICT); - response = allowed ? XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS : XDG_DESKTOP_PORTAL_RESPONSE_CANCELLED; g_debug ("Camera: sending response %d", response); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results)); - request_unexport (request); + xdp_request_unexport (request); } } +static const char * +app_id_from_app_info (XdpAppInfo *app_info) +{ + /* Automatically grant camera access to unsandboxed apps. */ + if (xdp_app_info_is_host (app_info)) + return ""; + + return xdp_app_info_get_id (app_info); +} + static gboolean handle_access_camera (XdpDbusCamera *object, GDBusMethodInvocation *invocation, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id; g_autoptr(GTask) task = NULL; @@ -120,12 +217,10 @@ handle_access_camera (XdpDbusCamera *object, REQUEST_AUTOLOCK (request); - app_id = xdp_app_info_get_id (request->app_info); - - + app_id = app_id_from_app_info (request->app_info); g_object_set_data_full (G_OBJECT (request), "app-id", g_strdup (app_id), g_free); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_camera_complete_access_camera (object, invocation, request->id); @@ -179,7 +274,7 @@ handle_open_pipewire_remote (XdpDbusCamera *object, { g_autoptr(XdpAppInfo) app_info = NULL; const char *app_id; - Permission permission; + XdpPermission permission; g_autoptr(GUnixFDList) out_fd_list = NULL; int fd; int fd_id; @@ -196,10 +291,10 @@ handle_open_pipewire_remote (XdpDbusCamera *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - app_info = xdp_invocation_lookup_app_info_sync (invocation, NULL, &error); - app_id = xdp_app_info_get_id (app_info); - permission = device_get_permission_sync (app_id, "camera"); - if (permission != PERMISSION_YES) + app_info = xdp_invocation_ensure_app_info_sync (invocation, NULL, &error); + app_id = app_id_from_app_info (app_info); + permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_DEVICE_CAMERA); + if (permission != XDP_PERMISSION_YES) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -445,11 +540,28 @@ camera_class_init (CameraClass *klass) GDBusInterfaceSkeleton * camera_create (GDBusConnection *connection, - gpointer lockdown_proxy) + const char *access_impl_dbus_name, + gpointer lockdown_proxy) { + g_autoptr(GError) error = NULL; + lockdown = lockdown_proxy; camera = g_object_new (camera_get_type (), NULL); + access_impl = xdp_dbus_impl_access_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + access_impl_dbus_name, + DESKTOP_PORTAL_OBJECT_PATH, + NULL, + &error); + if (access_impl == NULL) + { + g_warning ("Failed to create access proxy: %s", error->message); + return NULL; + } + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (access_impl), G_MAXINT); + return G_DBUS_INTERFACE_SKELETON (camera); } diff --git a/src/camera.h b/src/camera.h index 8163a99..12052e0 100644 --- a/src/camera.h +++ b/src/camera.h @@ -1,10 +1,12 @@ /* * Copyright © 2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -21,4 +23,5 @@ #include GDBusInterfaceSkeleton * camera_create (GDBusConnection *connection, + const char *access_impl_dbus_name, gpointer lockdown_proxy); diff --git a/src/clipboard.c b/src/clipboard.c index edf1358..beb6e91 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -1,10 +1,12 @@ /* * Copyright 2022 Google LLC * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -22,7 +24,7 @@ #include "clipboard.h" #include "remote-desktop.h" -#include "session.h" +#include "xdp-session.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -62,11 +64,11 @@ handle_request_clipboard (XdpDbusClipboard *object, const char *arg_session_handle, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; RemoteDesktopSession *remote_desktop_session; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -78,7 +80,7 @@ handle_request_clipboard (XdpDbusClipboard *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_remote_desktop_session (session)) + if (!IS_REMOTE_DESKTOP_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -86,7 +88,7 @@ handle_request_clipboard (XdpDbusClipboard *object, "Invalid session type"); return G_DBUS_METHOD_INVOCATION_HANDLED; } - remote_desktop_session = (RemoteDesktopSession *)session; + remote_desktop_session = REMOTE_DESKTOP_SESSION (session); if (!remote_desktop_session_can_request_clipboard (remote_desktop_session)) { @@ -110,13 +112,14 @@ handle_set_selection (XdpDbusClipboard *object, const char *arg_session_handle, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -128,7 +131,7 @@ handle_set_selection (XdpDbusClipboard *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_remote_desktop_session (session)) + if (!IS_REMOTE_DESKTOP_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -137,7 +140,7 @@ handle_set_selection (XdpDbusClipboard *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } else if (!remote_desktop_session_is_clipboard_enabled ( - (RemoteDesktopSession *)session)) + REMOTE_DESKTOP_SESSION (session))) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -146,7 +149,6 @@ handle_set_selection (XdpDbusClipboard *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, clipboard_set_selection_options, @@ -156,7 +158,7 @@ handle_set_selection (XdpDbusClipboard *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_clipboard_call_set_selection ( impl, arg_session_handle, options, NULL, NULL, NULL); @@ -176,8 +178,6 @@ selection_write_done (GObject *source_object, g_autoptr(GUnixFDList) fd_list = NULL; g_autoptr(GVariant) fd_handle = NULL; g_autoptr(GError) error = NULL; - int fd; - int fd_id; int out_fd_id = -1; if (!xdp_dbus_impl_clipboard_call_selection_write_finish ( @@ -191,24 +191,36 @@ selection_write_done (GObject *source_object, if (fd_handle) { - fd_id = g_variant_get_handle (fd_handle); - fd = g_unix_fd_list_get (fd_list, fd_id, &error); + int fd_id = g_variant_get_handle (fd_handle); - out_fd_id = g_unix_fd_list_append (out_fd_list, fd, &error); + if (fd_id < g_unix_fd_list_get_length (fd_list)) + { + g_autofd int fd = -1; - close (fd); + fd = g_unix_fd_list_get (fd_list, fd_id, &error); - if (out_fd_id == -1) + if (fd >= 0) + out_fd_id = g_unix_fd_list_append (out_fd_list, fd, &error); + } + else { - g_dbus_method_invocation_return_error ( - invocation, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_FAILED, - "Failed to append fd: %s", - error->message); + g_set_error_literal (&error, XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Bad file descriptor index"); } } + if (out_fd_id == -1) + { + g_dbus_method_invocation_return_error ( + invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_FAILED, + "Failed to append fd: %s", + error->message); + return; + } + xdp_dbus_clipboard_complete_selection_write ( NULL, invocation, @@ -223,10 +235,10 @@ handle_selection_write (XdpDbusClipboard *object, const char *arg_session_handle, guint arg_serial) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -238,7 +250,7 @@ handle_selection_write (XdpDbusClipboard *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_remote_desktop_session (session)) + if (!IS_REMOTE_DESKTOP_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -247,7 +259,7 @@ handle_selection_write (XdpDbusClipboard *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } else if (!remote_desktop_session_is_clipboard_enabled ( - (RemoteDesktopSession *)session)) + REMOTE_DESKTOP_SESSION (session))) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -274,10 +286,10 @@ handle_selection_write_done (XdpDbusClipboard *object, guint arg_serial, gboolean arg_success) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -289,7 +301,7 @@ handle_selection_write_done (XdpDbusClipboard *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_remote_desktop_session (session)) + if (!IS_REMOTE_DESKTOP_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -298,7 +310,7 @@ handle_selection_write_done (XdpDbusClipboard *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } else if (!remote_desktop_session_is_clipboard_enabled ( - (RemoteDesktopSession *)session)) + REMOTE_DESKTOP_SESSION (session))) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -325,10 +337,7 @@ selection_read_done (GObject *source_object, g_autoptr(GUnixFDList) fd_list = NULL; g_autoptr(GVariant) fd_handle = NULL; g_autoptr(GError) error = NULL; - - int fd; - int fd_id; - int out_fd_id; + int out_fd_id = -1; if (!xdp_dbus_impl_clipboard_call_selection_read_finish ( impl, &fd_handle, &fd_list, res, &error)) @@ -337,12 +346,28 @@ selection_read_done (GObject *source_object, g_warning ("A backend call failed: %s", error->message); } - fd_id = g_variant_get_handle (fd_handle); - fd = g_unix_fd_list_get (fd_list, fd_id, &error); - out_fd_list = g_unix_fd_list_new (); - out_fd_id = g_unix_fd_list_append (out_fd_list, fd, &error); - close (fd); + + if (fd_handle) + { + int fd_id = g_variant_get_handle (fd_handle); + + if (fd_id < g_unix_fd_list_get_length (fd_list)) + { + g_autofd int fd = -1; + + fd = g_unix_fd_list_get (fd_list, fd_id, &error); + + if (fd >= 0) + out_fd_id = g_unix_fd_list_append (out_fd_list, fd, &error); + } + else + { + g_set_error_literal (&error, XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Bad file descriptor index"); + } + } if (out_fd_id == -1) { @@ -351,6 +376,7 @@ selection_read_done (GObject *source_object, XDG_DESKTOP_PORTAL_ERROR_FAILED, "Failed to append fd: %s", error->message); + return; } xdp_dbus_clipboard_complete_selection_read ( @@ -364,10 +390,10 @@ handle_selection_read (XdpDbusClipboard *object, const char *arg_session_handle, const char *arg_mime_type) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -379,7 +405,7 @@ handle_selection_read (XdpDbusClipboard *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_remote_desktop_session (session)) + if (!IS_REMOTE_DESKTOP_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -388,7 +414,7 @@ handle_selection_read (XdpDbusClipboard *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } else if (!remote_desktop_session_is_clipboard_enabled ( - (RemoteDesktopSession *)session)) + REMOTE_DESKTOP_SESSION (session))) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -439,9 +465,9 @@ selection_transfer_cb (XdpDbusImplClipboard *impl, { GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); - Session *session; + XdpSession *session; - session = lookup_session (arg_session_handle); + session = xdp_session_lookup (arg_session_handle); if (!session) { g_warning ("Cannot find session"); @@ -450,8 +476,7 @@ selection_transfer_cb (XdpDbusImplClipboard *impl, SESSION_AUTOLOCK_UNREF (session); - RemoteDesktopSession *remote_desktop_session = - (RemoteDesktopSession *)session; + RemoteDesktopSession *remote_desktop_session = REMOTE_DESKTOP_SESSION (session); if (remote_desktop_session && remote_desktop_session_is_clipboard_enabled (remote_desktop_session) && @@ -476,9 +501,9 @@ selection_owner_changed_cb (XdpDbusImplClipboard *impl, { GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); - Session *session; + XdpSession *session; - session = lookup_session (arg_session_handle); + session = xdp_session_lookup (arg_session_handle); if (!session) { g_warning ("Cannot find session"); @@ -487,8 +512,7 @@ selection_owner_changed_cb (XdpDbusImplClipboard *impl, SESSION_AUTOLOCK_UNREF (session); - RemoteDesktopSession *remote_desktop_session = - (RemoteDesktopSession *)session; + RemoteDesktopSession *remote_desktop_session = REMOTE_DESKTOP_SESSION (session); if (remote_desktop_session && remote_desktop_session_is_clipboard_enabled (remote_desktop_session) && diff --git a/src/clipboard.h b/src/clipboard.h index a56ed3f..96f6446 100644 --- a/src/clipboard.h +++ b/src/clipboard.h @@ -1,10 +1,12 @@ /* * Copyright 2022 Google LLC * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -20,7 +22,7 @@ #include -#include "session.h" +#include "xdp-session.h" GDBusInterfaceSkeleton *clipboard_create (GDBusConnection *connection, const char *dbus_name); diff --git a/src/device.c b/src/device.c deleted file mode 100644 index de4cc56..0000000 --- a/src/device.c +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright © 2016 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Authors: - * Matthias Clasen - */ - -#include "config.h" - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include "device.h" -#include "request.h" -#include "permissions.h" -#include "xdp-dbus.h" -#include "xdp-impl-dbus.h" -#include "xdp-utils.h" - -#define PERMISSION_TABLE "devices" - -typedef struct _Device Device; -typedef struct _DeviceClass DeviceClass; - -struct _Device -{ - XdpDbusDeviceSkeleton parent_instance; -}; - -struct _DeviceClass -{ - XdpDbusDeviceSkeletonClass parent_class; -}; - -static XdpDbusImplAccess *impl; -static Device *device; -static XdpDbusImplLockdown *lockdown; - -GType device_get_type (void) G_GNUC_CONST; -static void device_iface_init (XdpDbusDeviceIface *iface); - -G_DEFINE_TYPE_WITH_CODE (Device, device, XDP_DBUS_TYPE_DEVICE_SKELETON, - G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_DEVICE, - device_iface_init)); - -static const char *known_devices[] = { - "microphone", - "speakers", - "camera", - NULL -}; - -Permission -device_get_permission_sync (const char *app_id, - const char *device) -{ - return get_permission_sync (app_id, PERMISSION_TABLE, device); -} - -gboolean -device_query_permission_sync (const char *app_id, - const char *device, - Request *request) -{ - Permission permission; - gboolean allowed; - - permission = device_get_permission_sync (app_id, device); - if (permission == PERMISSION_ASK || permission == PERMISSION_UNSET) - { - GVariantBuilder opt_builder; - g_autofree char *title = NULL; - g_autofree char *subtitle = NULL; - g_autofree char *body = NULL; - guint32 response = 2; - g_autoptr(GVariant) results = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GAppInfo) info = NULL; - g_autoptr(XdpDbusImplRequest) impl_request = NULL; - - if (app_id[0] != 0) - { - g_autofree char *desktop_id; - desktop_id = g_strconcat (app_id, ".desktop", NULL); - info = (GAppInfo*)g_desktop_app_info_new (desktop_id); - } - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - - if (strcmp (device, "microphone") == 0) - { - g_variant_builder_add (&opt_builder, "{sv}", "icon", g_variant_new_string ("audio-input-microphone-symbolic")); - - title = g_strdup (_("Turn On Microphone?")); - body = g_strdup (_("Access to your microphone can be changed " - "at any time from the privacy settings.")); - - if (info == NULL) - subtitle = g_strdup (_("An application wants to use your microphone.")); - else - subtitle = g_strdup_printf (_("%s wants to use your microphone."), g_app_info_get_display_name (info)); - } - else if (strcmp (device, "speakers") == 0) - { - g_variant_builder_add (&opt_builder, "{sv}", "icon", g_variant_new_string ("audio-speakers-symbolic")); - - title = g_strdup (_("Turn On Speakers?")); - body = g_strdup (_("Access to your speakers can be changed " - "at any time from the privacy settings.")); - - if (info == NULL) - subtitle = g_strdup (_("An application wants to play sound.")); - else - subtitle = g_strdup_printf (_("%s wants to play sound."), g_app_info_get_display_name (info)); - } - else if (strcmp (device, "camera") == 0) - { - g_variant_builder_add (&opt_builder, "{sv}", "icon", g_variant_new_string ("camera-web-symbolic")); - - title = g_strdup (_("Turn On Camera?")); - body = g_strdup (_("Access to your camera can be changed " - "at any time from the privacy settings.")); - - if (info == NULL) - subtitle = g_strdup (_("An application wants to use your camera.")); - else - subtitle = g_strdup_printf (_("%s wants to use your camera."), g_app_info_get_display_name (info)); - } - - impl_request = xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, - g_dbus_proxy_get_name (G_DBUS_PROXY (impl)), - request->id, - NULL, &error); - if (!impl_request) - return FALSE; - - request_set_impl_request (request, impl_request); - - g_debug ("Calling backend for device access to: %s", device); - - if (!xdp_dbus_impl_access_call_access_dialog_sync (impl, - request->id, - app_id, - "", - title, - subtitle, - body, - g_variant_builder_end (&opt_builder), - &response, - &results, - NULL, - &error)) - { - g_warning ("A backend call failed: %s", error->message); - /* We do not want to set the permission if there was an error and the - * permission was UNSET. Setting a permission should only be done from - * user input. */ - return FALSE; - } - - allowed = response == 0; - - if (permission == PERMISSION_UNSET) - set_permission_sync (app_id, PERMISSION_TABLE, device, allowed ? PERMISSION_YES : PERMISSION_NO); - } - else - allowed = permission == PERMISSION_YES ? TRUE : FALSE; - - return allowed; -} - -static void -handle_access_device_in_thread (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Request *request = (Request *)task_data; - const char *app_id; - const char *device; - gboolean allowed; - - REQUEST_AUTOLOCK (request); - - app_id = (const char *)g_object_get_data (G_OBJECT (request), "app-id"); - device = (const char *)g_object_get_data (G_OBJECT (request), "device"); - - allowed = device_query_permission_sync (app_id, device, request); - - if (request->exported) - { - GVariantBuilder results; - - g_variant_builder_init (&results, G_VARIANT_TYPE_VARDICT); - xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), - allowed ? XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS - : XDG_DESKTOP_PORTAL_RESPONSE_CANCELLED, - g_variant_builder_end (&results)); - request_unexport (request); - } -} - -static gboolean -handle_access_device (XdpDbusDevice *object, - GDBusMethodInvocation *invocation, - guint32 pid, - const char * const *devices, - GVariant *arg_options) -{ - Request *request = request_from_invocation (invocation); - g_autoptr(XdpAppInfo) app_info = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(XdpDbusImplRequest) impl_request = NULL; - g_autoptr(GTask) task = NULL; - - if (g_strv_length ((char **)devices) != 1 || !g_strv_contains (known_devices, devices[0])) - { - g_dbus_method_invocation_return_error (invocation, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "Invalid devices requested"); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - - if (g_str_equal (devices[0], "microphone") && - xdp_dbus_impl_lockdown_get_disable_microphone (lockdown)) - { - g_debug ("Microphone access disabled"); - g_dbus_method_invocation_return_error (invocation, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, - "Microphone access disabled"); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - if (g_str_equal (devices[0], "camera") && - xdp_dbus_impl_lockdown_get_disable_camera (lockdown)) - { - g_debug ("Camera access disabled"); - g_dbus_method_invocation_return_error (invocation, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, - "Camera access disabled"); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - if (g_str_equal (devices[0], "speakers") && - xdp_dbus_impl_lockdown_get_disable_sound_output (lockdown)) - { - g_debug ("Speaker access disabled"); - g_dbus_method_invocation_return_error (invocation, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, - "Speaker access disabled"); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - - REQUEST_AUTOLOCK (request); - - if (!xdp_app_info_is_host (request->app_info)) - { - g_dbus_method_invocation_return_error (invocation, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, - "This call is not available inside the sandbox"); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - - app_info = xdp_get_app_info_from_pid (pid, &error); - if (app_info == NULL) - { - g_dbus_method_invocation_return_error (invocation, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "Invalid pid requested"); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - - g_object_set_data_full (G_OBJECT (request), "app-id", g_strdup (xdp_app_info_get_id (app_info)), g_free); - g_object_set_data_full (G_OBJECT (request), "device", g_strdup (devices[0]), g_free); - - impl_request = xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, - g_dbus_proxy_get_name (G_DBUS_PROXY (impl)), - request->id, - NULL, &error); - if (!impl_request) - { - g_dbus_method_invocation_return_gerror (invocation, error); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - xdp_dbus_device_complete_access_device (object, invocation, request->id); - - task = g_task_new (object, NULL, NULL, NULL); - g_task_set_task_data (task, g_object_ref (request), g_object_unref); - g_task_run_in_thread (task, handle_access_device_in_thread); - - return G_DBUS_METHOD_INVOCATION_HANDLED; -} - -static void -device_iface_init (XdpDbusDeviceIface *iface) -{ - iface->handle_access_device = handle_access_device; -} - -static void -device_init (Device *device) -{ - xdp_dbus_device_set_version (XDP_DBUS_DEVICE (device), 1); -} - -static void -device_class_init (DeviceClass *klass) -{ -} - -GDBusInterfaceSkeleton * -device_create (GDBusConnection *connection, - const char *dbus_name, - gpointer lockdown_proxy) -{ - g_autoptr(GError) error = NULL; - - lockdown = lockdown_proxy; - - impl = xdp_dbus_impl_access_proxy_new_sync (connection, - G_DBUS_PROXY_FLAGS_NONE, - dbus_name, - DESKTOP_PORTAL_OBJECT_PATH, - NULL, - &error); - if (impl == NULL) - { - g_warning ("Failed to create access proxy: %s", error->message); - return NULL; - } - - g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (impl), G_MAXINT); - - device = g_object_new (device_get_type (), NULL); - - return G_DBUS_INTERFACE_SKELETON (device); -} diff --git a/src/device.h b/src/device.h deleted file mode 100644 index 74d1efb..0000000 --- a/src/device.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2016 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Authors: - * Matthias Clasen - */ - -#pragma once - -#include - -#include "request.h" -#include "permissions.h" - -Permission device_get_permission_sync (const char *app_id, - const char *device); - -gboolean device_query_permission_sync (const char *app_id, - const char *device, - Request *request); - -GDBusInterfaceSkeleton * device_create (GDBusConnection *connection, - const char *dbus_name, - gpointer lockdown); diff --git a/src/documents.h b/src/documents.h deleted file mode 100644 index a5b53a7..0000000 --- a/src/documents.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright © 2016 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Authors: - * Matthias Clasen - */ - -#pragma once - -#include - -typedef enum { - DOCUMENT_FLAG_NONE = 0, - DOCUMENT_FLAG_FOR_SAVE = (1 << 0), - DOCUMENT_FLAG_WRITABLE = (1 << 1), - DOCUMENT_FLAG_DIRECTORY = (1 << 2), - DOCUMENT_FLAG_DELETABLE = (1 << 3), -} DocumentFlags; - -void init_document_proxy (GDBusConnection *connection); - -char *register_document (const char *uri, - const char *app_id, - DocumentFlags flags, - GError **error); - -char *get_real_path_for_doc_path (const char *path, - XdpAppInfo *app_info); - -char *get_real_path_for_doc_id (const char *doc_id); diff --git a/src/dynamic-launcher.c b/src/dynamic-launcher.c index c7c5091..75be8df 100644 --- a/src/dynamic-launcher.c +++ b/src/dynamic-launcher.c @@ -1,10 +1,12 @@ /* * Copyright © 2022 Matthew Leeds * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -35,9 +37,10 @@ #include #include +#include "xdp-call.h" #include "dynamic-launcher.h" -#include "request.h" -#include "call.h" +#include "xdp-app-launch-context.h" +#include "xdp-request.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -106,11 +109,11 @@ get_launcher_data_and_revoke_token (const char *token) } static gboolean -validate_desktop_file_id (const char *app_id, +validate_desktop_file_id (XdpAppInfo *app_info, const char *desktop_file_id, GError **error) { - const char *after_app_id; + g_autofree char *no_dot_desktop = NULL; if (!g_str_has_suffix (desktop_file_id, ".desktop")) { @@ -120,22 +123,51 @@ validate_desktop_file_id (const char *app_id, return FALSE; } - if (app_id == NULL || *app_id == '\0') - return TRUE; + no_dot_desktop = g_strndup (desktop_file_id, + strlen(desktop_file_id) - strlen (".desktop")); - after_app_id = desktop_file_id + strlen (app_id); - if (!g_str_has_prefix (desktop_file_id, app_id) || *after_app_id != '.') + if (!xdp_app_info_is_valid_sub_app_id (app_info, no_dot_desktop)) { g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - _("Desktop file id missing app id prefix '%s.': %s"), - app_id, desktop_file_id); + _("Desktop file id is not valid")); return FALSE; } return TRUE; } +static gboolean +validate_serialized_icon (GVariant *arg_icon_v, + char **icon_format, + char **icon_size) +{ + GBytes *bytes; + g_autoptr(GIcon) icon = NULL; + g_autoptr(GVariant) icon_v = NULL; + g_autoptr(XdpSealedFd) sealed_icon = NULL; + + if (!arg_icon_v) + return FALSE; + + if (!(icon_v = g_variant_get_variant (arg_icon_v))) + return FALSE; + + icon = g_icon_deserialize (icon_v); + + if (!G_IS_BYTES_ICON (icon)) + return FALSE; + + bytes = g_bytes_icon_get_bytes (G_BYTES_ICON (icon)); + sealed_icon = xdp_sealed_fd_new_from_bytes (bytes, NULL); + + return sealed_icon && + xdp_validate_icon (sealed_icon, + XDP_ICON_TYPE_DESKTOP, + icon_format, + icon_size); +} + static gboolean write_icon_to_disk (GVariant *icon_v, const char *icon_subdir, @@ -172,35 +204,98 @@ write_icon_to_disk (GVariant *icon_v, return TRUE; } +static void +get_icon_path (const char *desktop_file_id, + const char *icon_extension, + const char *icon_size, + char **icon_dir_path_out, + char **icon_path_out) +{ + g_autofree char *no_dot_desktop = NULL; + g_autofree char *icon_name = NULL; + g_autofree char *subdir = NULL; + + no_dot_desktop = g_strndup (desktop_file_id, + strlen(desktop_file_id) - strlen (".desktop")); + icon_name = g_strconcat (no_dot_desktop, ".", icon_extension, NULL); + + /* Put the icon in a per-size subdirectory so the size is discernible + * without reading the file + */ + if (g_strcmp0 (icon_extension, "svg") == 0) + subdir = g_strdup ("scalable"); + else + subdir = g_strdup_printf ("%sx%s", icon_size, icon_size); + + *icon_dir_path_out = g_build_filename (g_get_user_data_dir (), + XDG_PORTAL_ICONS_DIR, + subdir, + NULL); + *icon_path_out = g_build_filename (*icon_dir_path_out, + icon_name, + NULL); +} + +static gboolean +write_keyfile_to_disk (const char *desktop_file_id, + GKeyFile *desktop_keyfile, + GError **error) +{ + g_autofree char *desktop_dir = NULL; + g_autofree char *desktop_path = NULL; + g_autofree char *link_dir = NULL; + g_autofree char *link_path = NULL; + g_autofree char *relative_path = NULL; + g_autoptr(GFile) link_file = NULL; + + /* Put the desktop file in ~/.local/share/xdg-desktop-portal/applications/ so + * there's no ambiguity about which launchers were created by this portal. + */ + desktop_dir = g_build_filename (g_get_user_data_dir (), + XDG_PORTAL_APPLICATIONS_DIR, + NULL); + g_mkdir_with_parents (desktop_dir, 0700); + desktop_path = g_build_filename (desktop_dir, desktop_file_id, NULL); + + if (!g_key_file_save_to_file (desktop_keyfile, desktop_path, error)) + return FALSE; + + /* Make a sym link in ~/.local/share/applications so the launcher shows up in + * the desktop environment's menu. + */ + link_dir = g_build_filename (g_get_user_data_dir (), + "applications", + NULL); + g_mkdir_with_parents (link_dir, 0700); + link_path = g_build_filename (link_dir, desktop_file_id, NULL); + + relative_path = g_build_filename ("..", + XDG_PORTAL_APPLICATIONS_DIR, + desktop_file_id, + NULL); + + link_file = g_file_new_for_path (link_path); + g_file_delete (link_file, NULL, NULL); + + if (!g_file_make_symbolic_link (link_file, relative_path, NULL, error)) + { + remove (desktop_path); + return FALSE; + } + + return TRUE; +} + static GKeyFile * -save_icon_and_get_desktop_entry (const char *desktop_file_id, - const char *desktop_entry, - GVariant *launcher_data, - XdpAppInfo *xdp_app_info, - char **out_icon_path, - GError **error) +get_desktop_entry (const char *desktop_file_id, + const char *desktop_entry, + const char *name, + const char *app_id, + char *icon_path, + GError **error) { - g_autoptr(GVariant) icon_v = NULL; - g_autoptr(GDesktopAppInfo) desktop_app_info = NULL; g_autoptr(GKeyFile) key_file = g_key_file_new (); - g_autofree char *exec = NULL; - g_auto(GStrv) exec_strv = NULL; - g_auto(GStrv) prefixed_exec_strv = NULL; g_auto(GStrv) groups = NULL; - g_autofree char *prefixed_exec = NULL; - g_autofree char *tryexec_path = NULL; - g_autofree char *icon_path = NULL; - g_autofree char *icon_subdir = NULL; - const char *name, *icon_extension, *icon_size; - const char *app_id; - - g_variant_get (launcher_data, "(&sv&s&s)", &name, &icon_v, &icon_extension, &icon_size); - g_assert (name != NULL && name[0] != '\0'); - g_assert (icon_v); - g_assert (icon_extension != NULL && icon_extension[0] != '\0'); - g_assert (icon_size != NULL && icon_size[0] != '\0'); - - app_id = xdp_app_info_get_id (xdp_app_info); if (!g_key_file_load_from_data (key_file, desktop_entry, -1, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, @@ -216,97 +311,18 @@ save_icon_and_get_desktop_entry (const char *desktop_file_id, * there's a security risk. */ groups = g_key_file_get_groups (key_file, NULL); - if (g_strv_length (groups) > 1 || !g_strv_contains ((const char * const *)groups, G_KEY_FILE_DESKTOP_GROUP)) + if (g_strv_length (groups) != 1 || + !g_strv_contains ((const char * const *)groups, G_KEY_FILE_DESKTOP_GROUP)) { g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - _("Desktop entry given to Install() must have only one group")); + _("Desktop entry given to Install() must have exactly one group")); return NULL; } /* Overwrite Name= and Icon= if they are present */ g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "Name", name); - - { - g_autofree char *no_dot_desktop = NULL; - g_autofree char *icon_name = NULL; - g_autofree char *subdir = NULL; - - no_dot_desktop = g_strndup (desktop_file_id, strlen(desktop_file_id) - strlen (".desktop")); - icon_name = g_strconcat (no_dot_desktop, ".", icon_extension, NULL); - - /* Put the icon in a per-size subdirectory so the size is discernible - * without reading the file - */ - if (g_strcmp0 (icon_extension, "svg") == 0) - subdir = g_strdup ("scalable"); - else - subdir = g_strdup_printf ("%sx%s", icon_size, icon_size); - - icon_subdir = g_build_filename (g_get_user_data_dir (), XDG_PORTAL_ICONS_DIR, subdir, NULL); - icon_path = g_build_filename (icon_subdir, icon_name, NULL); - - g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "Icon", icon_path); - } - - exec = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "Exec", error); - if (exec == NULL) - return NULL; - - if (!g_shell_parse_argv (exec, NULL, &exec_strv, error)) - return NULL; - - /* Don't let the app give itself access to host files */ - if (xdp_app_info_get_kind (xdp_app_info) == XDP_APP_INFO_KIND_FLATPAK && - g_strv_contains ((const char * const *)exec_strv, "--file-forwarding")) - { - g_set_error (error, - XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - _("Desktop entry given to Install() must not use --file-forwarding")); - return NULL; - } - - prefixed_exec_strv = xdp_app_info_rewrite_commandline (xdp_app_info, - (const char * const *)exec_strv, - TRUE /* quote escape */); - if (prefixed_exec_strv == NULL) - { - g_set_error (error, - XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_FAILED, - _("DynamicLauncher install not supported for: %s"), app_id); - return NULL; - } - - prefixed_exec = g_strjoinv (" ", prefixed_exec_strv); - g_key_file_set_value (key_file, G_KEY_FILE_DESKTOP_GROUP, "Exec", prefixed_exec); - - tryexec_path = xdp_app_info_get_tryexec_path (xdp_app_info); - if (tryexec_path != NULL) - g_key_file_set_value (key_file, G_KEY_FILE_DESKTOP_GROUP, "TryExec", tryexec_path); - - if (xdp_app_info_get_kind (xdp_app_info) == XDP_APP_INFO_KIND_FLATPAK) - { - /* Flatpak checks for this key */ - g_key_file_set_value (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-Flatpak", app_id); - /* Flatpak removes this one for security */ - g_key_file_remove_key (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Bugzilla-ExtraInfoScript", NULL); - } - - desktop_app_info = g_desktop_app_info_new_from_keyfile (key_file); - if (desktop_app_info == NULL) - { - g_set_error (error, - XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - _("Desktop entry given to Install() not valid")); - return NULL; - } - - /* Write the icon last so it's only on-disk if other checks passed */ - if (!write_icon_to_disk (icon_v, icon_subdir, icon_path, error)) - return NULL; - - if (out_icon_path) - *out_icon_path = g_steal_pointer (&icon_path); + g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "Icon", icon_path); return g_steal_pointer (&key_file); } @@ -319,18 +335,17 @@ handle_install (XdpDbusDynamicLauncher *object, const gchar *arg_desktop_entry, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (call->app_info); g_autoptr(GVariant) launcher_data = NULL; g_autoptr(GError) error = NULL; g_autoptr(GKeyFile) desktop_keyfile = NULL; - g_autofree char *icon_path = NULL; - g_autofree char *desktop_dir = NULL; - g_autofree char *desktop_path = NULL; - g_autofree char *link_path = NULL; - g_autofree char *relative_path = NULL; - g_autoptr(GFile) link_file = NULL; gsize desktop_entry_length = G_MAXSIZE; + g_autoptr(GVariant) icon_v = NULL; + const char *name, *icon_extension, *icon_size; + g_autofree char *icon_dir_path = NULL; + g_autofree char *icon_path = NULL; + g_autoptr(GDesktopAppInfo) desktop_app_info = NULL; launcher_data = get_launcher_data_and_revoke_token (arg_token); if (launcher_data == NULL) @@ -342,25 +357,50 @@ handle_install (XdpDbusDynamicLauncher *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } + g_variant_get (launcher_data, "(&sv&s&s)", &name, &icon_v, &icon_extension, &icon_size); + g_assert (name != NULL && name[0] != '\0'); + g_assert (icon_v); + g_assert (icon_extension != NULL && icon_extension[0] != '\0'); + g_assert (icon_size != NULL && icon_size[0] != '\0'); - if (!validate_desktop_file_id (app_id, arg_desktop_file_id, &error)) + if (!validate_desktop_file_id (call->app_info, arg_desktop_file_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - desktop_keyfile = save_icon_and_get_desktop_entry (arg_desktop_file_id, - arg_desktop_entry, - launcher_data, - call->app_info, - &icon_path, - &error); + get_icon_path (arg_desktop_file_id, icon_extension, icon_size, + &icon_dir_path, &icon_path); + + desktop_keyfile = get_desktop_entry (arg_desktop_file_id, + arg_desktop_entry, + name, + app_id, + icon_path, + &error); if (desktop_keyfile == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } + if (!xdp_app_info_validate_dynamic_launcher (call->app_info, + desktop_keyfile, + &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + desktop_app_info = g_desktop_app_info_new_from_keyfile (desktop_keyfile); + if (desktop_app_info == NULL) + { + g_set_error (&error, + XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + _("Desktop entry given to Install() not valid")); + goto error; + } + g_free (g_key_file_to_data (desktop_keyfile, &desktop_entry_length, NULL)); if (desktop_entry_length > MAX_DESKTOP_SIZE_BYTES) { @@ -371,33 +411,21 @@ handle_install (XdpDbusDynamicLauncher *object, goto error; } - /* Put the desktop file in ~/.local/share/xdg-desktop-portal/applications/ so - * there's no ambiguity about which launchers were created by this portal. - */ - desktop_dir = g_build_filename (g_get_user_data_dir (), XDG_PORTAL_APPLICATIONS_DIR, NULL); - g_mkdir_with_parents (desktop_dir, 0700); - desktop_path = g_build_filename (desktop_dir, arg_desktop_file_id, NULL); - if (!g_key_file_save_to_file (desktop_keyfile, desktop_path, &error)) + /* Write the files last so it's only on-disk if other checks passed */ + if (!write_icon_to_disk (icon_v, icon_dir_path, icon_path, &error)) goto error; - /* Make a sym link in ~/.local/share/applications so the launcher shows up in - * the desktop environment's menu. - */ - link_path = g_build_filename (g_get_user_data_dir (), "applications", arg_desktop_file_id, NULL); - link_file = g_file_new_for_path (link_path); - relative_path = g_build_filename ("..", XDG_PORTAL_APPLICATIONS_DIR, arg_desktop_file_id, NULL); - g_file_delete (link_file, NULL, NULL); - if (!g_file_make_symbolic_link (link_file, relative_path, NULL, &error)) - goto error; + if (!write_keyfile_to_disk (arg_desktop_file_id, desktop_keyfile, &error)) + { + remove (icon_path); + goto error; + } xdp_dbus_dynamic_launcher_complete_install (object, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; error: g_dbus_method_invocation_return_gerror (invocation, error); - remove (icon_path); - remove (desktop_path); - remove (link_path); return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -411,10 +439,10 @@ static gboolean install_token_timeout (gpointer data) { g_autoptr(GVariant) launcher_data = NULL; + const char *token = data; - g_debug ("Revoking install token %s", (char *)data); - launcher_data = get_launcher_data_and_revoke_token ((char *)data); - g_free (data); + g_debug ("Revoking install token %s", token); + launcher_data = get_launcher_data_and_revoke_token (token); return G_SOURCE_REMOVE; } @@ -425,7 +453,7 @@ set_launcher_data_for_token (const char *token, { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&transient_permissions_lock); guint timeout_id; - GVariant *launcher_data_wrapped; + g_autoptr(GVariant) launcher_data_wrapped = NULL; if (!transient_permissions) { @@ -438,11 +466,12 @@ set_launcher_data_for_token (const char *token, */ timeout_id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 300, install_token_timeout, g_strdup (token), g_free); - launcher_data_wrapped = g_variant_new ("(vu)", launcher_data, timeout_id); + launcher_data_wrapped = + g_variant_ref_sink (g_variant_new ("(vu)", launcher_data, timeout_id)); g_hash_table_insert (transient_permissions, g_strdup (token), - g_variant_ref_sink (launcher_data_wrapped)); + g_steal_pointer (&launcher_data_wrapped)); } static void @@ -450,17 +479,15 @@ prepare_install_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; - GVariant *launcher_data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) results = NULL; g_autoptr(GError) error = NULL; - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); REQUEST_AUTOLOCK (request); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - if (!xdp_dbus_impl_dynamic_launcher_call_prepare_install_finish (XDP_DBUS_IMPL_DYNAMIC_LAUNCHER (source), &response, &results, @@ -478,7 +505,7 @@ prepare_install_done (GObject *source, const char *chosen_name = NULL; const char *icon_format = NULL; const char *icon_size = NULL; - GVariant *chosen_icon = NULL; + g_autoptr(GVariant) chosen_icon = NULL; icon_format = g_object_get_data (G_OBJECT (request), "icon-format"); g_assert (icon_format != NULL && icon_format[0] != '\0'); @@ -498,6 +525,8 @@ prepare_install_done (GObject *source, } else { + GVariant *launcher_data; + /* Save the token in memory and return it to the caller */ launcher_data = g_variant_new ("(svss)", chosen_name, chosen_icon, icon_format, icon_size); set_launcher_data_for_token (token, launcher_data); @@ -512,11 +541,7 @@ prepare_install_done (GObject *source, response, g_variant_builder_end (&results_builder)); - request_unexport (request); - } - else - { - g_variant_builder_clear (&results_builder); + xdp_request_unexport (request); } } @@ -588,11 +613,12 @@ handle_prepare_install (XdpDbusDynamicLauncher *object, GVariant *arg_icon_v, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autofree char *icon_format = NULL; g_autofree char *icon_size = NULL; g_autoptr(GVariant) icon_v = NULL; @@ -610,10 +636,9 @@ handle_prepare_install (XdpDbusDynamicLauncher *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &opt_builder, prepare_install_options, G_N_ELEMENTS (prepare_install_options), &error)) { @@ -622,9 +647,7 @@ handle_prepare_install (XdpDbusDynamicLauncher *object, } /* Do some validation on the icon before passing it along */ - icon_v = g_variant_get_variant (arg_icon_v); - if (!icon_v || !xdp_validate_serialized_icon (icon_v, TRUE /* bytes_only */, - &icon_format, &icon_size)) + if (!validate_serialized_icon (arg_icon_v, &icon_format, &icon_size)) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -659,10 +682,9 @@ handle_request_install_token (XdpDbusDynamicLauncher *object, GVariant *arg_icon_v, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (call->app_info); g_autoptr(GError) error = NULL; - GVariant *launcher_data; g_autofree char *token = NULL; g_autofree char *icon_format = NULL; g_autofree char *icon_size = NULL; @@ -692,10 +714,10 @@ handle_request_install_token (XdpDbusDynamicLauncher *object, if (response == 0) { + GVariant *launcher_data; + /* Do some validation on the icon before saving it */ - icon_v = g_variant_get_variant (arg_icon_v); - if (!icon_v || !xdp_validate_serialized_icon (icon_v, TRUE /* bytes_only */, - &icon_format, &icon_size)) + if (!validate_serialized_icon (arg_icon_v, &icon_format, &icon_size)) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -729,8 +751,7 @@ handle_uninstall (XdpDbusDynamicLauncher *object, const gchar *arg_desktop_file_id, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - const char *app_id = xdp_app_info_get_id (call->app_info); + XdpCall *call = xdp_call_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(GError) desktop_file_error = NULL; g_autofree char *icon_dir = NULL; @@ -741,7 +762,7 @@ handle_uninstall (XdpDbusDynamicLauncher *object, g_autoptr(GFile) link_file = NULL; g_autoptr(GKeyFile) desktop_keyfile = NULL; - if (!validate_desktop_file_id (app_id, arg_desktop_file_id, &error)) + if (!validate_desktop_file_id (call->app_info, arg_desktop_file_id, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; @@ -788,15 +809,14 @@ handle_get_desktop_entry (XdpDbusDynamicLauncher *object, GDBusMethodInvocation *invocation, const gchar *arg_desktop_file_id) { - Call *call = call_from_invocation (invocation); - const char *app_id = xdp_app_info_get_id (call->app_info); + XdpCall *call = xdp_call_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autofree char *desktop_dir = NULL; g_autofree char *contents = NULL; g_autofree char *desktop_path = NULL; gsize length; - if (!validate_desktop_file_id (app_id, arg_desktop_file_id, &error)) + if (!validate_desktop_file_id (call->app_info, arg_desktop_file_id, &error)) goto error; desktop_dir = g_build_filename (g_get_user_data_dir (), XDG_PORTAL_APPLICATIONS_DIR, NULL); @@ -826,8 +846,7 @@ handle_get_icon (XdpDbusDynamicLauncher *object, GDBusMethodInvocation *invocation, const gchar *arg_desktop_file_id) { - Call *call = call_from_invocation (invocation); - const char *app_id = xdp_app_info_get_id (call->app_info); + XdpCall *call = xdp_call_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autofree char *desktop_dir = NULL; g_autofree char *contents = NULL; @@ -845,7 +864,7 @@ handle_get_icon (XdpDbusDynamicLauncher *object, const gchar *icon_format = NULL; int icon_size = 0; - if (!validate_desktop_file_id (app_id, arg_desktop_file_id, &error)) + if (!validate_desktop_file_id (call->app_info, arg_desktop_file_id, &error)) goto error; desktop_dir = g_build_filename (g_get_user_data_dir (), XDG_PORTAL_APPLICATIONS_DIR, NULL); @@ -941,16 +960,16 @@ handle_launch (XdpDbusDynamicLauncher *object, const gchar *arg_desktop_file_id, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - const char *app_id = xdp_app_info_get_id (call->app_info); + XdpCall *call = xdp_call_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autofree char *desktop_dir = NULL; g_autofree char *desktop_path = NULL; const char *activation_token = NULL; - g_autoptr(GAppLaunchContext) launch_context = NULL; + g_autoptr(XdpAppLaunchContext) xdp_launch_context = NULL; + GAppLaunchContext *launch_context = NULL; g_autoptr(GDesktopAppInfo) app_info = NULL; - if (!validate_desktop_file_id (app_id, arg_desktop_file_id, &error)) + if (!validate_desktop_file_id (call->app_info, arg_desktop_file_id, &error)) goto error; desktop_dir = g_build_filename (g_get_user_data_dir (), XDG_PORTAL_APPLICATIONS_DIR, NULL); @@ -964,14 +983,15 @@ handle_launch (XdpDbusDynamicLauncher *object, goto error; } + g_variant_lookup (arg_options, "activation_token", "&s", &activation_token); + + xdp_launch_context = xdp_app_launch_context_new (); + launch_context = G_APP_LAUNCH_CONTEXT (xdp_launch_context); /* Unset env var set in main() */ - launch_context = g_app_launch_context_new (); g_app_launch_context_unsetenv (launch_context, "GIO_USE_VFS"); - /* Set activation token for focus stealing prevention */ - g_variant_lookup (arg_options, "activation_token", "&s", &activation_token); - if (activation_token) - g_app_launch_context_setenv (launch_context, "XDG_ACTIVATION_TOKEN", activation_token); + xdp_app_launch_context_set_activation_token (xdp_launch_context, + activation_token); app_info = g_desktop_app_info_new_from_filename (desktop_path); if (app_info == NULL) diff --git a/src/dynamic-launcher.h b/src/dynamic-launcher.h index f95089d..ef557c6 100644 --- a/src/dynamic-launcher.h +++ b/src/dynamic-launcher.h @@ -1,10 +1,12 @@ /* * Copyright © 2022 Matthew Leeds * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/email.c b/src/email.c index e38e8a8..19df100 100644 --- a/src/email.c +++ b/src/email.c @@ -1,10 +1,12 @@ /* * Copyright © 2017 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -33,8 +35,8 @@ #include #include "email.h" -#include "request.h" -#include "documents.h" +#include "xdp-request.h" +#include "xdp-documents.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -68,11 +70,8 @@ send_response_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = task_data; + XdpRequest *request = task_data; guint response; - GVariantBuilder new_results; - - g_variant_builder_init (&new_results, G_VARIANT_TYPE_VARDICT); REQUEST_AUTOLOCK (request); @@ -80,10 +79,13 @@ send_response_in_thread_func (GTask *task, if (request->exported) { + g_auto(GVariantBuilder) new_results = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&new_results)); - request_unexport (request); + xdp_request_unexport (request); } } @@ -92,7 +94,7 @@ compose_email_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) results = NULL; g_autoptr(GError) error = NULL; @@ -207,11 +209,12 @@ handle_compose_email (XdpDbusEmail *object, const gchar *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options; + g_auto(GVariantBuilder) options = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GVariant) attachment_fds = NULL; g_debug ("Handling ComposeEmail"); @@ -229,22 +232,29 @@ handle_compose_email (XdpDbusEmail *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); - attachment_fds = g_variant_lookup_value (arg_options, "attachment_fds", G_VARIANT_TYPE ("ah")); if (attachment_fds) { - GVariantBuilder attachments; + g_auto(GVariantBuilder) attachments = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_STRING_ARRAY); int i; - g_variant_builder_init (&attachments, G_VARIANT_TYPE_STRING_ARRAY); for (i = 0; i < g_variant_n_children (attachment_fds); i++) { g_autofree char *path = NULL; int fd_id; - int fd; + g_autofd int fd = -1; g_variant_get_child (attachment_fds, i, "h", &fd_id); + if (fd_id >= g_unix_fd_list_get_length (fd_list)) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Bad file descriptor index"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + fd = g_unix_fd_list_get (fd_list, fd_id, &error); if (fd == -1) { @@ -260,7 +270,8 @@ handle_compose_email (XdpDbusEmail *object, /* Don't leak any info about real file path existence, etc */ g_dbus_method_invocation_return_error (invocation, - XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, "Invalid attachment fd passed"); return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -280,8 +291,8 @@ handle_compose_email (XdpDbusEmail *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_email_complete_compose_email (object, invocation, NULL, request->id); diff --git a/src/email.h b/src/email.h index 16c0c02..98d3229 100644 --- a/src/email.h +++ b/src/email.h @@ -1,10 +1,12 @@ /* * Copyright © 2017 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/file-chooser.c b/src/file-chooser.c index ab5ceca..7beebf4 100644 --- a/src/file-chooser.c +++ b/src/file-chooser.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -33,8 +35,8 @@ #include #include "file-chooser.h" -#include "request.h" -#include "documents.h" +#include "xdp-request.h" +#include "xdp-documents.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -70,26 +72,25 @@ send_response_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = task_data; - GVariantBuilder results; - GVariantBuilder ruris; + XdpRequest *request = task_data; + g_auto(GVariantBuilder) results = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_auto(GVariantBuilder) ruris = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_STRING_ARRAY); guint response; GVariant *options; - DocumentFlags flags = DOCUMENT_FLAG_WRITABLE | DOCUMENT_FLAG_DIRECTORY; + XdpDocumentFlags flags = XDP_DOCUMENT_FLAG_WRITABLE | XDP_DOCUMENT_FLAG_DIRECTORY; g_autofree char **uris = NULL; GVariant *choices; GVariant *current_filter; GVariant *writable; - g_variant_builder_init (&results, G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&ruris, G_VARIANT_TYPE_STRING_ARRAY); - REQUEST_AUTOLOCK (request); if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "for-save")) == TRUE) - flags |= DOCUMENT_FLAG_FOR_SAVE; + flags |= XDP_DOCUMENT_FLAG_FOR_SAVE; if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "directory")) == FALSE) - flags &= ~DOCUMENT_FLAG_DIRECTORY; + flags &= ~XDP_DOCUMENT_FLAG_DIRECTORY; response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "response")); options = (GVariant *)g_object_get_data (G_OBJECT (request), "options"); @@ -98,7 +99,7 @@ send_response_in_thread_func (GTask *task, writable = g_variant_lookup_value (options, "writable", G_VARIANT_TYPE("b")); if (writable && !g_variant_get_boolean (writable)) - flags &= ~DOCUMENT_FLAG_WRITABLE; + flags &= ~XDP_DOCUMENT_FLAG_WRITABLE; choices = g_variant_lookup_value (options, "choices", G_VARIANT_TYPE ("a(ss)")); if (choices) @@ -117,10 +118,18 @@ send_response_in_thread_func (GTask *task, g_autofree char *ruri = NULL; g_autoptr(GError) error = NULL; + g_assert (uris[i] != NULL); + + if (!g_str_has_prefix (uris[i], "file://")) + { + g_warning ("Only URIs with the \"file://\" scheme are allowed"); + continue; + } + if (xdp_app_info_is_host (request->app_info)) ruri = g_strdup (uris[i]); else - ruri = register_document (uris[i], xdp_app_info_get_id (request->app_info), flags, &error); + ruri = xdp_register_document (uris[i], xdp_app_info_get_id (request->app_info), flags, &error); if (ruri == NULL) { @@ -140,7 +149,7 @@ send_response_in_thread_func (GTask *task, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results)); - request_unexport (request); + xdp_request_unexport (request); } g_task_return_boolean (task, TRUE); @@ -184,7 +193,7 @@ looks_like_document_portal_path (const char *path, static char * get_host_folder_for_doc_id (const char *doc_id) { - g_autofree char *real_path = get_real_path_for_doc_id (doc_id); + g_autofree char *real_path = xdp_get_real_path_for_doc_id (doc_id); g_autofree char *host_folder = NULL; if (real_path != NULL) @@ -198,7 +207,7 @@ open_file_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; @@ -517,18 +526,18 @@ handle_open_file (XdpDbusFileChooser *object, const gchar *arg_title, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options; + g_auto(GVariantBuilder) options = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GVariant) dir_option = NULL; g_debug ("Handling OpenFile"); REQUEST_AUTOLOCK (request); - g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options, open_file_options, G_N_ELEMENTS (open_file_options), &error)) @@ -579,8 +588,8 @@ handle_open_file (XdpDbusFileChooser *object, if (dir_option && g_variant_get_boolean (dir_option)) g_object_set_data (G_OBJECT (request), "directory", GINT_TO_POINTER (TRUE)); - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_impl_file_chooser_call_open_file (impl, request->id, @@ -614,7 +623,7 @@ save_file_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; @@ -646,11 +655,12 @@ handle_save_file (XdpDbusFileChooser *object, const gchar *arg_title, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; XdpDbusImplRequest *impl_request; - GVariantBuilder options; + g_auto(GVariantBuilder) options = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_debug ("Handling SaveFile"); @@ -666,7 +676,6 @@ handle_save_file (XdpDbusFileChooser *object, REQUEST_AUTOLOCK (request); - g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options, save_file_options, G_N_ELEMENTS (save_file_options), &error)) @@ -683,13 +692,13 @@ handle_save_file (XdpDbusFileChooser *object, if (value) { const char *path = g_variant_get_bytestring (value); - g_autofree char *host_path = get_real_path_for_doc_path (path, request->app_info); + g_autofree char *host_path = xdp_get_real_path_for_doc_path (path, request->app_info); g_autofree char *doc_id = NULL; if (strcmp (path, host_path) == 0 && looks_like_document_portal_path (path, &doc_id)) { - char *real_path = get_real_path_for_doc_id (doc_id); + char *real_path = xdp_get_real_path_for_doc_id (doc_id); if (real_path) { @@ -741,8 +750,8 @@ handle_save_file (XdpDbusFileChooser *object, g_object_set_data (G_OBJECT (request), "for-save", GINT_TO_POINTER (TRUE)); - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_impl_file_chooser_call_save_file (impl, request->id, @@ -773,7 +782,7 @@ save_files_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; @@ -805,11 +814,12 @@ handle_save_files (XdpDbusFileChooser *object, const gchar *arg_title, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; XdpDbusImplRequest *impl_request; - GVariantBuilder options; + g_auto(GVariantBuilder) options = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); if (xdp_dbus_impl_lockdown_get_disable_save_to_disk (lockdown)) { @@ -823,7 +833,6 @@ handle_save_files (XdpDbusFileChooser *object, REQUEST_AUTOLOCK (request); - g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options, save_files_options, G_N_ELEMENTS (save_files_options), &error)) @@ -846,8 +855,8 @@ handle_save_files (XdpDbusFileChooser *object, g_object_set_data (G_OBJECT (request), "for-save", GINT_TO_POINTER (TRUE)); - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_impl_file_chooser_call_save_files (impl, request->id, @@ -875,7 +884,7 @@ file_chooser_iface_init (XdpDbusFileChooserIface *iface) static void file_chooser_init (FileChooser *fc) { - xdp_dbus_file_chooser_set_version (XDP_DBUS_FILE_CHOOSER (fc), 3); + xdp_dbus_file_chooser_set_version (XDP_DBUS_FILE_CHOOSER (fc), 4); } static void diff --git a/src/file-chooser.h b/src/file-chooser.h index be86c61..916b2b5 100644 --- a/src/file-chooser.h +++ b/src/file-chooser.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/gamemode.c b/src/gamemode.c index 3c761f8..4431690 100644 --- a/src/gamemode.c +++ b/src/gamemode.c @@ -1,10 +1,12 @@ /* * Copyright © 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -20,9 +22,10 @@ #include "config.h" -#include "request.h" -#include "permissions.h" +#include "xdp-call.h" +#include "xdp-permissions.h" +#include "xdp-app-info.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -163,7 +166,7 @@ game_mode_is_allowed_for_app (const char *app_id, GError **error) const char **stored; gboolean ok; - ok = xdp_dbus_impl_permission_store_call_lookup_sync (get_permission_store (), + ok = xdp_dbus_impl_permission_store_call_lookup_sync (xdp_get_permission_store (), PERMISSION_TABLE, PERMISSION_ID, &perms, @@ -239,7 +242,7 @@ call_data_new (GDBusMethodInvocation *inv, call = g_slice_new0 (CallData); call->inv = g_object_ref (inv); - call->app_info = xdp_app_info_ref (app_info); + call->app_info = g_object_ref (app_info); call->method = g_strdup (method); return call; @@ -252,10 +255,10 @@ call_data_free (gpointer data) if (call == NULL) return; - g_object_unref (call->inv); - xdp_app_info_unref (call->app_info); - g_free (call->method); - g_clear_object(&call->fdlist); + g_clear_object (&call->inv); + g_clear_object (&call->app_info); + g_clear_pointer (&call->method, g_free); + g_clear_object (&call->fdlist); g_slice_free (CallData, call); } @@ -289,15 +292,22 @@ handle_call_thread (GTask *task, { pid_t pids[2] = {0, }; guint n_pids; + ino_t pidns_id; n_pids = call->n_ids; for (guint i = 0; i < n_pids; i++) pids[0] = (pid_t) call->ids[i]; - ok = xdp_app_info_map_pids (call->app_info, pids, n_pids, &error); + if (!xdp_app_info_get_pidns (call->app_info, &pidns_id, &error)) + { + g_prefix_error (&error, "Could not get pidns: "); + g_warning ("GameMode error: %s", error->message); + g_dbus_method_invocation_return_gerror (call->inv, error); + return; + } - if (!ok) + if (pidns_id != 0 && !xdp_map_pids (pidns_id, pids, n_pids, &error)) { g_prefix_error (&error, "Could not map pids: "); g_warning ("GameMode error: %s", error->message); @@ -322,7 +332,7 @@ handle_call_thread (GTask *task, /* verify fds are actually pidfds */ fds = g_unix_fd_list_peek_fds (fdlist, &n_pids); - ok = xdp_app_info_pidfds_to_pids (call->app_info, fds, pids, n_pids, &error); + ok = xdp_pidfds_to_pids (fds, pids, n_pids, &error); if (!ok || !check_pids (pids, n_pids, &error)) { @@ -365,8 +375,8 @@ handle_call_in_thread_fds (XdpDbusGameMode *object, { g_autoptr(GTask) task = NULL; XdpAppInfo *app_info; - Request *request; - CallData *call; + XdpCall *call; + CallData *call_data; if (fdlist == NULL || g_unix_fd_list_get_length (fdlist) != 2) { @@ -375,15 +385,15 @@ handle_call_in_thread_fds (XdpDbusGameMode *object, return; } - request = request_from_invocation (invocation); - app_info = request->app_info; + call = xdp_call_from_invocation (invocation); + app_info = call->app_info; - call = call_data_new (invocation, app_info, method); - call->fdlist = g_object_ref (fdlist); + call_data = call_data_new (invocation, app_info, method); + call_data->fdlist = g_object_ref (fdlist); task = g_task_new (object, NULL, NULL, NULL); - g_task_set_task_data (task, call, call_data_free); + g_task_set_task_data (task, call_data, call_data_free); g_task_run_in_thread (task, handle_call_thread); } @@ -396,26 +406,26 @@ handle_call_in_thread (XdpDbusGameMode *object, { g_autoptr(GTask) task = NULL; XdpAppInfo *app_info; - Request *request; - CallData *call; + XdpCall *call; + CallData *call_data; - request = request_from_invocation (invocation); - app_info = request->app_info; + call = xdp_call_from_invocation (invocation); + app_info = call->app_info; - call = call_data_new (invocation, app_info, method); + call_data = call_data_new (invocation, app_info, method); - call->ids[0] = target; - call->n_ids = 1; + call_data->ids[0] = target; + call_data->n_ids = 1; if (requester != 0) { - call->ids[1] = requester; - call->n_ids += 1; + call_data->ids[1] = requester; + call_data->n_ids += 1; } task = g_task_new (object, NULL, NULL, NULL); - g_task_set_task_data (task, call, call_data_free); + g_task_set_task_data (task, call_data, call_data_free); g_task_run_in_thread (task, handle_call_thread); } diff --git a/src/gamemode.h b/src/gamemode.h index e53fa13..6b9d205 100644 --- a/src/gamemode.h +++ b/src/gamemode.h @@ -1,10 +1,12 @@ /* * Copyright © 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/generate-method-info.py b/src/generate-method-info.py new file mode 100755 index 0000000..c71ac24 --- /dev/null +++ b/src/generate-method-info.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +import argparse +import xml.etree.ElementTree as ElementTree + + +def quote(s: str): + return f'"{s}"' + + +def cbool(b: bool): + return "TRUE" if b else "FALSE" + + +def handle_interface(interface: ElementTree.Element): + intf_name = interface.attrib["name"] + for method in interface.iter("method"): + method_name = method.attrib["name"] + uses_requests = False + option_arg = -1 + + for pos, arg in enumerate(method.iter("arg")): + arg_name = arg.attrib["name"] + arg_type = arg.attrib["type"] + arg_direction = arg.attrib.get("direction", "in") + if ( + (arg_name == "handle" or arg_name == "request_handle") + and arg_type == "o" + and arg_direction == "out" + ): + uses_requests = True + + if arg_name == "options" and arg_type == "a{sv}" and arg_direction == "in": + option_arg = pos + + method_name = quote(method_name) + iname = quote(intf_name) + print( + f" {{ .interface = {iname:40s}, .method = {method_name:32s}, .uses_request = {cbool(uses_requests)}, .option_arg = {option_arg:2d}, }}," + ) + + +def parse_portal_xml(filename: str): + tree = ElementTree.parse(filename) + root = tree.getroot() + + for interface in root.iter("interface"): + handle_interface(interface) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("file", type=str, nargs="+") + + args = parser.parse_args() + + print('#include "glib.h"') + print('#include "xdp-method-info.h"') + print("") + print("static const XdpMethodInfo method_info[] = {") + + for file in args.file: + parse_portal_xml(file) + + print(" { .interface = NULL },") + print("};") + print("") + print( + "const XdpMethodInfo *xdp_method_info_get_all (void) { return method_info; };" + ) + print("") + print( + "unsigned int xdp_method_info_get_count (void) { return G_N_ELEMENTS(method_info) - 1; };" + ) diff --git a/src/glib-backports.c b/src/glib-backports.c deleted file mode 100644 index c4d7207..0000000 --- a/src/glib-backports.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright © 2014 Red Hat, Inc - * Copyright © 2021 Joshua Lee - * Copyright © 2021 Emmanuel Fleury - * Copyright © 2021 Nelson Ben - * Copyright © 2021 Peter Bloomfield - * Copyright © 2021 Collabora Ltd. - * Copyright 2023 Igalia - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - */ - -#include "config.h" - -#include - -#include "glib-backports.h" - -#if !GLIB_CHECK_VERSION (2, 68, 0) -/* All this code is backported directly from glib */ -guint -g_string_replace (GString *string, - const gchar *find, - const gchar *replace, - guint limit) -{ - gsize f_len, r_len, pos; - gchar *cur, *next; - guint n = 0; - - g_return_val_if_fail (string != NULL, 0); - g_return_val_if_fail (find != NULL, 0); - g_return_val_if_fail (replace != NULL, 0); - - f_len = strlen (find); - r_len = strlen (replace); - cur = string->str; - - while ((next = strstr (cur, find)) != NULL) - { - pos = next - string->str; - g_string_erase (string, pos, f_len); - g_string_insert (string, pos, replace); - cur = string->str + pos + r_len; - n++; - /* Only match the empty string once at any given position, to - * avoid infinite loops */ - if (f_len == 0) - { - if (cur[0] == '\0') - break; - else - cur++; - } - if (n == limit) - break; - } - - return n; -} -#endif /* GLIB_CHECK_VERSION (2, 68, 0) */ diff --git a/src/glib-backports.h b/src/glib-backports.h index 0ac5993..cc44363 100644 --- a/src/glib-backports.h +++ b/src/glib-backports.h @@ -1,10 +1,12 @@ /* * Copyright © 2014, 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -22,21 +24,46 @@ #pragma once #include +#include -#if !GLIB_CHECK_VERSION (2, 68, 0) -guint g_string_replace (GString *string, - const gchar *find, - const gchar *replace, - guint limit); -#endif +#if !GLIB_CHECK_VERSION(2, 76, 0) + +static inline gboolean +g_clear_fd (int *fd_ptr, + GError **error) +{ + int fd = *fd_ptr; + + *fd_ptr = -1; + + if (fd < 0) + return TRUE; + + /* Suppress "Not available before" warning */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + return g_close (fd, error); + G_GNUC_END_IGNORE_DEPRECATIONS +} -#if !GLIB_CHECK_VERSION (2, 68, 0) static inline void -g_log_writer_default_set_use_stderr (gboolean use_stderr) +_g_clear_fd_ignore_error (int *fd_ptr) { - /* Does nothing because outside of the tests we don't really care that it - * doesn't work correctly after this call and those tests can run on newer - * GLibs - */ + /* Don't overwrite thread-local errno if closing the fd fails */ + int errsv = errno; + + /* Suppress "Not available before" warning */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + + if (!g_clear_fd (fd_ptr, NULL)) + { + /* Do nothing: we ignore all errors, except for EBADF which + * is a programming error, checked for by g_close(). */ + } + + G_GNUC_END_IGNORE_DEPRECATIONS + + errno = errsv; } + +#define g_autofd __attribute__((cleanup(_g_clear_fd_ignore_error))) #endif diff --git a/src/global-shortcuts.c b/src/global-shortcuts.c index 8e40fd6..3866408 100644 --- a/src/global-shortcuts.c +++ b/src/global-shortcuts.c @@ -1,10 +1,12 @@ /* * Copyright © 2022 Aleix Pol Gonzalez * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,9 +26,9 @@ #include #include "global-shortcuts.h" -#include "request.h" -#include "session.h" -#include "permissions.h" +#include "xdp-request.h" +#include "xdp-session.h" +#include "xdp-permissions.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -57,24 +59,37 @@ G_DEFINE_TYPE_WITH_CODE (GlobalShortcuts, global_shortcuts, XDP_DBUS_TYPE_GLOBAL typedef struct _GlobalShortcutsSession { - Session parent; + XdpSession parent; gboolean closed; } GlobalShortcutsSession; typedef struct _GlobalShortcutsSessionClass { - SessionClass parent_class; + XdpSessionClass parent_class; } GlobalShortcutsSessionClass; GType global_shortcuts_session_get_type (void); -G_DEFINE_TYPE (GlobalShortcutsSession, global_shortcuts_session, session_get_type ()) +G_DEFINE_TYPE (GlobalShortcutsSession, global_shortcuts_session, xdp_session_get_type ()) + +G_GNUC_UNUSED static inline GlobalShortcutsSession * +GLOBAL_SHORTCUTS_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_CAST (ptr, global_shortcuts_session_get_type (), GlobalShortcutsSession); +} + +G_GNUC_UNUSED static inline gboolean +IS_GLOBAL_SHORTCUTS_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_TYPE (ptr, global_shortcuts_session_get_type ()); +} static void -global_shortcuts_session_close (Session *session) +global_shortcuts_session_close (XdpSession *session) { - GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session; + GlobalShortcutsSession *global_shortcuts_session = + GLOBAL_SHORTCUTS_SESSION (session); global_shortcuts_session->closed = TRUE; } @@ -94,21 +109,21 @@ static void global_shortcuts_session_class_init (GlobalShortcutsSessionClass *klass) { GObjectClass *object_class; - SessionClass *session_class; + XdpSessionClass *session_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = global_shortcuts_session_finalize; - session_class = (SessionClass *)klass; + session_class = (XdpSessionClass *)klass; session_class->close = global_shortcuts_session_close; } static GlobalShortcutsSession * global_shortcuts_session_new (GVariant *options, - Request *request, + XdpRequest *request, GError **error) { - Session *session; + XdpSession *session; GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (request); const char *session_token; @@ -131,7 +146,7 @@ global_shortcuts_session_new (GVariant *options, if (session) g_debug ("global shortcuts session owned by '%s' created", session->sender); - return (GlobalShortcutsSession *) session; + return GLOBAL_SHORTCUTS_SESSION (session); } static void @@ -139,12 +154,13 @@ session_created_cb (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; guint response = 2; g_autoptr (GVariant) results = NULL; gboolean should_close_session; - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GError) error = NULL; REQUEST_AUTOLOCK (request); @@ -153,8 +169,6 @@ session_created_cb (GObject *source_object, SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - if (!xdp_dbus_impl_global_shortcuts_call_create_session_finish (impl, &response, &results, @@ -169,7 +183,7 @@ session_created_cb (GObject *source_object, if (request->exported && response == 0) { - if (!session_export (session, &error)) + if (!xdp_session_export (session, &error)) { g_warning ("Failed to export session: %s", error->message); response = 2; @@ -178,7 +192,7 @@ session_created_cb (GObject *source_object, } should_close_session = FALSE; - session_register (session); + xdp_session_register (session); } else { @@ -194,15 +208,11 @@ session_created_cb (GObject *source_object, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results_builder)); - request_unexport (request); - } - else - { - g_variant_builder_clear (&results_builder); + xdp_request_unexport (request); } if (should_close_session) - session_close (session, FALSE); + xdp_session_close (session, FALSE); } static XdpOptionKey global_shortcuts_create_session_options[] = { @@ -215,27 +225,26 @@ handle_create_session (XdpDbusGlobalShortcuts *object, GDBusMethodInvocation *invocation, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options_builder; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GVariant) options = NULL; - Session *session; + XdpSession *session; REQUEST_AUTOLOCK (request); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, global_shortcuts_create_session_options, G_N_ELEMENTS (global_shortcuts_create_session_options), &error)) { - g_variant_builder_clear(&options_builder); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); impl_request = xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, @@ -248,10 +257,10 @@ handle_create_session (XdpDbusGlobalShortcuts *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - session = (Session *)global_shortcuts_session_new (options, request, &error); + session = XDP_SESSION (global_shortcuts_session_new (options, request, &error)); if (!session) { g_dbus_method_invocation_return_gerror (invocation, error); @@ -267,7 +276,7 @@ handle_create_session (XdpDbusGlobalShortcuts *object, request->id, session->id, xdp_app_info_get_id (request->app_info), - g_steal_pointer (&options), + options, NULL, session_created_cb, g_object_ref (request)); @@ -282,8 +291,8 @@ shortcuts_bound_cb (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; guint response = 2; g_autoptr(GError) error = NULL; g_autoptr(GVariant) results = NULL; @@ -304,14 +313,14 @@ shortcuts_bound_cb (GObject *source_object, { if (!results) { - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); } } @@ -332,7 +341,8 @@ xdp_verify_shortcuts (GVariant *shortcuts, iter = g_variant_iter_new (shortcuts); while (g_variant_iter_loop (iter, "(s@a{sv})", &shortcut_name, &values)) { - GVariantBuilder shortcut_builder; + g_auto(GVariantBuilder) shortcut_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); if (shortcut_name[0] == 0) { @@ -343,7 +353,6 @@ xdp_verify_shortcuts (GVariant *shortcuts, return FALSE; } - g_variant_builder_init (&shortcut_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (values, &shortcut_builder, global_shortcuts_keys, G_N_ELEMENTS (global_shortcuts_keys), @@ -368,41 +377,39 @@ handle_bind_shortcuts (XdpDbusGlobalShortcuts *object, const gchar *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); - Session *session; + XdpRequest *request = xdp_request_from_invocation (invocation); + XdpSession *session; g_autoptr(XdpDbusImplRequest) impl_request = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) options = NULL; g_autoptr(GVariant) shortcuts = NULL; - GVariantBuilder shortcuts_builder; - GVariantBuilder options_builder; + g_auto(GVariantBuilder) shortcuts_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY); + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); REQUEST_AUTOLOCK (request); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, global_shortcuts_bind_shortcuts_options, G_N_ELEMENTS (global_shortcuts_bind_shortcuts_options), &error)) { - g_variant_builder_clear (&options_builder); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); - g_variant_builder_init (&shortcuts_builder, G_VARIANT_TYPE_ARRAY); if (!xdp_verify_shortcuts (arg_shortcuts, &shortcuts_builder, &error)) { - g_variant_builder_clear (&shortcuts_builder); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - shortcuts = g_variant_builder_end (&shortcuts_builder); + shortcuts = g_variant_ref_sink (g_variant_builder_end (&shortcuts_builder)); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -426,8 +433,8 @@ handle_bind_shortcuts (XdpDbusGlobalShortcuts *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -437,9 +444,9 @@ handle_bind_shortcuts (XdpDbusGlobalShortcuts *object, xdp_dbus_impl_global_shortcuts_call_bind_shortcuts (impl, request->id, arg_session_handle, - g_steal_pointer (&shortcuts), + shortcuts, arg_parent_window, - g_steal_pointer (&options), + options, NULL, shortcuts_bound_cb, g_object_ref (request)); @@ -454,8 +461,8 @@ shortcuts_listed_cb (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; guint response = 2; g_autoptr(GError) error = NULL; g_autoptr(GVariant) results = NULL; @@ -476,14 +483,14 @@ shortcuts_listed_cb (GObject *source_object, { if (!results) { - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); } } @@ -497,29 +504,28 @@ handle_list_shortcuts (XdpDbusGlobalShortcuts *object, const gchar *arg_session_handle, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); - Session *session; + XdpRequest *request = xdp_request_from_invocation (invocation); + XdpSession *session; g_autoptr(XdpDbusImplRequest) impl_request = NULL; g_autoptr(GError) error = NULL; - GVariantBuilder options_builder; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, global_shortcuts_list_shortcuts_options, G_N_ELEMENTS (global_shortcuts_list_shortcuts_options), &error)) { - g_variant_builder_clear (&options_builder); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -541,8 +547,8 @@ handle_list_shortcuts (XdpDbusGlobalShortcuts *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -590,8 +596,9 @@ activated_cb (XdpDbusImplGlobalShortcuts *impl, gpointer data) { GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); - g_autoptr(Session) session = lookup_session (session_id); - GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session; + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); + GlobalShortcutsSession *global_shortcuts_session = + GLOBAL_SHORTCUTS_SESSION (session); g_debug ("Received activated %s for %s", session_id, shortcut_id); @@ -616,8 +623,9 @@ deactivated_cb (XdpDbusImplGlobalShortcuts *impl, gpointer data) { GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); - g_autoptr(Session) session = lookup_session (session_id); - GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session; + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); + GlobalShortcutsSession *global_shortcuts_session = + GLOBAL_SHORTCUTS_SESSION (session); g_debug ("Received deactivated %s for %s", session_id, shortcut_id); @@ -640,8 +648,9 @@ shortcuts_changed_cb (XdpDbusImplGlobalShortcuts *impl, gpointer data) { GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); - g_autoptr(Session) session = lookup_session (session_id); - GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session; + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); + GlobalShortcutsSession *global_shortcuts_session = + GLOBAL_SHORTCUTS_SESSION (session); g_debug ("Received ShortcutsChanged %s", session_id); diff --git a/src/global-shortcuts.h b/src/global-shortcuts.h index beab1a2..1591d1c 100644 --- a/src/global-shortcuts.h +++ b/src/global-shortcuts.h @@ -1,10 +1,12 @@ /* * Copyright © 2022 Aleix Pol Gonzalez * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/inhibit.c b/src/inhibit.c index e64772f..19d477d 100644 --- a/src/inhibit.c +++ b/src/inhibit.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,9 +26,9 @@ #include #include "inhibit.h" -#include "request.h" -#include "session.h" -#include "permissions.h" +#include "xdp-request.h" +#include "xdp-session.h" +#include "xdp-permissions.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -72,7 +74,7 @@ inhibit_done (GObject *source, gpointer data) { g_autoptr(GError) error = NULL; - Request *request = data; + XdpRequest *request = data; int response = 0; REQUEST_AUTOLOCK (request); @@ -86,9 +88,8 @@ inhibit_done (GObject *source, if (request->exported) { - GVariantBuilder new_results; - - g_variant_builder_init (&new_results, G_VARIANT_TYPE_VARDICT); + g_auto(GVariantBuilder) new_results = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, @@ -102,7 +103,7 @@ get_allowed_inhibit (const char *app_id) g_auto(GStrv) perms = NULL; guint32 ret = 0; - perms = get_permissions_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); + perms = xdp_get_permissions_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); if (perms != NULL) { @@ -136,7 +137,7 @@ handle_inhibit_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; + XdpRequest *request = XDP_REQUEST (task_data); const char *window; guint32 flags; GVariant *options; @@ -195,11 +196,12 @@ handle_inhibit (XdpDbusInhibit *object, guint32 arg_flags, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; g_autoptr(GTask) task = NULL; - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); @@ -213,7 +215,6 @@ handle_inhibit (XdpDbusInhibit *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_filter_options (arg_options, &opt_builder, inhibit_options, G_N_ELEMENTS (inhibit_options), NULL); @@ -235,8 +236,8 @@ handle_inhibit (XdpDbusInhibit *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); task = g_task_new (object, NULL, NULL, NULL); g_task_set_task_data (task, g_object_ref (request), g_object_unref); @@ -249,24 +250,36 @@ handle_inhibit (XdpDbusInhibit *object, typedef struct _InhibitSession { - Session parent; + XdpSession parent; gboolean closed; } InhibitSession; typedef struct _InhibitSessionClass { - SessionClass parent_class; + XdpSessionClass parent_class; } InhibitSessionClass; GType inhibit_session_get_type (void); -G_DEFINE_TYPE (InhibitSession, inhibit_session, session_get_type ()) +G_DEFINE_TYPE (InhibitSession, inhibit_session, xdp_session_get_type ()) + +G_GNUC_UNUSED static inline InhibitSession * +INHIBIT_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_CAST (ptr, inhibit_session_get_type (), InhibitSession); +} + +G_GNUC_UNUSED static inline gboolean +IS_INHIBIT_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_TYPE (ptr, inhibit_session_get_type ()); +} static void -inhibit_session_close (Session *session) +inhibit_session_close (XdpSession *session) { - InhibitSession *inhibit_session = (InhibitSession *)session; + InhibitSession *inhibit_session = INHIBIT_SESSION (session); inhibit_session->closed = TRUE; @@ -288,21 +301,21 @@ static void inhibit_session_class_init (InhibitSessionClass *klass) { GObjectClass *object_class; - SessionClass *session_class; + XdpSessionClass *session_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = inhibit_session_finalize; - session_class = (SessionClass *)klass; + session_class = (XdpSessionClass *)klass; session_class->close = inhibit_session_close; } static InhibitSession * inhibit_session_new (GVariant *options, - Request *request, + XdpRequest *request, GError **error) { - Session *session; + XdpSession *session; const char *session_token; GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (request); GDBusConnection *connection = g_dbus_interface_skeleton_get_connection (interface_skeleton); @@ -330,11 +343,12 @@ create_monitor_done (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; guint response = 2; gboolean should_close_session; - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GError) error = NULL; REQUEST_AUTOLOCK (request); @@ -343,8 +357,6 @@ create_monitor_done (GObject *source_object, SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_object_set_data (G_OBJECT (request), "session", NULL); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - if (!xdp_dbus_impl_inhibit_call_create_monitor_finish (impl, &response, res, &error)) { g_dbus_error_strip_remote_error (error); @@ -355,7 +367,7 @@ create_monitor_done (GObject *source_object, if (request->exported && response == 0) { - if (!session_export (session, &error)) + if (!xdp_session_export (session, &error)) { g_warning ("Failed to export session: %s", error->message); response = 2; @@ -364,7 +376,7 @@ create_monitor_done (GObject *source_object, } should_close_session = FALSE; - session_register (session); + xdp_session_register (session); } else { @@ -380,15 +392,11 @@ create_monitor_done (GObject *source_object, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results_builder)); - request_unexport (request); - } - else - { - g_variant_builder_clear (&results_builder); + xdp_request_unexport (request); } if (should_close_session) - session_close (session, FALSE); + xdp_session_close (session, FALSE); } static gboolean @@ -397,10 +405,10 @@ handle_create_monitor (XdpDbusInhibit *object, const char *arg_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - Session *session; + XdpSession *session; REQUEST_AUTOLOCK (request); @@ -416,10 +424,10 @@ handle_create_monitor (XdpDbusInhibit *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - session = (Session *)inhibit_session_new (arg_options, request, &error); + session = XDP_SESSION (inhibit_session_new (arg_options, request, &error)); if (!session) { g_dbus_method_invocation_return_gerror (invocation, error); @@ -447,7 +455,7 @@ handle_query_end_response (XdpDbusInhibit *object, GDBusMethodInvocation *invocation, const char *session_id) { - g_autoptr(Session) session = lookup_session (session_id); + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); if (!session) { @@ -492,8 +500,8 @@ state_changed_cb (XdpDbusImplInhibit *impl, gpointer data) { GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); - g_autoptr(Session) session = lookup_session (session_id); - InhibitSession *inhibit_session = (InhibitSession *)session; + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); + InhibitSession *inhibit_session = INHIBIT_SESSION (session); gboolean active = FALSE; guint32 session_state = 0; diff --git a/src/inhibit.h b/src/inhibit.h index 886f913..96048c0 100644 --- a/src/inhibit.h +++ b/src/inhibit.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/input-capture.c b/src/input-capture.c index f9682f1..29a605b 100644 --- a/src/input-capture.c +++ b/src/input-capture.c @@ -1,10 +1,12 @@ /* * Copyright © 2017-2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -22,9 +24,9 @@ #include #include -#include "session.h" +#include "xdp-session.h" #include "input-capture.h" -#include "request.h" +#include "xdp-request.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -68,32 +70,38 @@ typedef enum _InputCaptureSessionState typedef struct _InputCaptureSession { - Session parent; + XdpSession parent; InputCaptureSessionState state; } InputCaptureSession; typedef struct _InputCaptureSessionClass { - SessionClass parent_class; + XdpSessionClass parent_class; } InputCaptureSessionClass; GType input_capture_session_get_type (void); -G_DEFINE_TYPE (InputCaptureSession, input_capture_session, session_get_type ()) +G_DEFINE_TYPE (InputCaptureSession, input_capture_session, xdp_session_get_type ()) -static gboolean -is_input_capture_session (Session *session) +G_GNUC_UNUSED static inline InputCaptureSession * +INPUT_CAPTURE_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_CAST (ptr, input_capture_session_get_type (), InputCaptureSession); +} + +G_GNUC_UNUSED static inline gboolean +IS_INPUT_CAPTURE_SESSION (gpointer ptr) { - return G_TYPE_CHECK_INSTANCE_TYPE (session, input_capture_session_get_type ()); + return G_TYPE_CHECK_INSTANCE_TYPE (ptr, input_capture_session_get_type ()); } static InputCaptureSession * -input_capture_session_new (GVariant *options, - Request *request, - GError **error) +input_capture_session_new (GVariant *options, + XdpRequest *request, + GError **error) { - Session *session; + XdpSession *session; GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (request); const char *session_token; @@ -124,11 +132,12 @@ create_session_done (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; g_autoptr(GError) error = NULL; - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); GVariant *results; - Session *session; + XdpSession *session; gboolean should_close_session; uint32_t capabilities = 0; uint32_t response = 2; @@ -139,8 +148,6 @@ create_session_done (GObject *source_object, SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - if (!xdp_dbus_impl_input_capture_call_create_session_finish (impl, &response, &results, @@ -155,7 +162,7 @@ create_session_done (GObject *source_object, if (request->exported && response == 0) { - if (!session_export (session, &error)) + if (!xdp_session_export (session, &error)) { g_warning ("Failed to export session: %s", error->message); response = 2; @@ -172,7 +179,7 @@ create_session_done (GObject *source_object, } should_close_session = FALSE; - session_register (session); + xdp_session_register (session); g_variant_builder_add (&results_builder, "{sv}", "capabilities", g_variant_new_uint32 (capabilities)); @@ -190,15 +197,11 @@ create_session_done (GObject *source_object, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results_builder)); - request_unexport (request); - } - else - { - g_variant_builder_clear (&results_builder); + xdp_request_unexport (request); } if (should_close_session) - session_close (session, FALSE); + xdp_session_close (session, FALSE); } static gboolean @@ -229,12 +232,13 @@ handle_create_session (XdpDbusInputCapture *object, const char *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(XdpDbusImplRequest) impl_request = NULL; g_autoptr(GError) error = NULL; - GVariantBuilder options_builder; - GVariant *options; - Session *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; + XdpSession *session; REQUEST_AUTOLOCK (request); @@ -250,17 +254,16 @@ handle_create_session (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - session = (Session *)input_capture_session_new (arg_options, request, &error); + session = XDP_SESSION (input_capture_session_new (arg_options, request, &error)); if (!session) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, input_capture_create_session_options, G_N_ELEMENTS (input_capture_create_session_options), @@ -269,7 +272,7 @@ handle_create_session (XdpDbusInputCapture *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -297,13 +300,13 @@ get_zones_done (GObject *source_object, gpointer data) { g_autoptr(GVariant) results = NULL; - g_autoptr(Request) request = NULL; + g_autoptr(XdpRequest) request = NULL; g_autoptr(GError) error = NULL; - Session *session; + XdpSession *session; gboolean should_close_session; uint32_t response = 2; - request = (Request *)data; + request = XDP_REQUEST (data); REQUEST_AUTOLOCK (request); @@ -325,21 +328,21 @@ get_zones_done (GObject *source_object, if (request->exported) { - if (response != 0) + if (!results) { - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - results = g_variant_builder_end (&results_builder); + results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); } if (should_close_session) { - session_close (session, TRUE); + xdp_session_close (session, TRUE); } } @@ -352,17 +355,18 @@ handle_get_zones (XdpDbusInputCapture *object, const char *arg_session_handle, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(XdpDbusImplRequest) impl_request = NULL; InputCaptureSession *input_capture_session; g_autoptr(GError) error = NULL; - GVariantBuilder options_builder; - GVariant *options; - Session *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; + XdpSession *session; REQUEST_AUTOLOCK (request); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -374,7 +378,7 @@ handle_get_zones (XdpDbusInputCapture *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -383,7 +387,7 @@ handle_get_zones (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - input_capture_session = (InputCaptureSession *)session; + input_capture_session = INPUT_CAPTURE_SESSION (session); switch (input_capture_session->state) { @@ -412,10 +416,9 @@ handle_get_zones (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, input_capture_get_zones_options, G_N_ELEMENTS (input_capture_get_zones_options), @@ -425,7 +428,7 @@ handle_get_zones (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -452,13 +455,13 @@ set_pointer_barriers_done (GObject *source_object, gpointer data) { g_autoptr(GVariant) results = NULL; - g_autoptr(Request) request = NULL; + g_autoptr(XdpRequest) request = NULL; g_autoptr(GError) error = NULL; gboolean should_close_session; - Session *session; + XdpSession *session; uint32_t response = 2; - request = (Request *)data; + request = XDP_REQUEST (data); REQUEST_AUTOLOCK (request); @@ -471,29 +474,30 @@ set_pointer_barriers_done (GObject *source_object, &results, res, &error)) - { + { g_dbus_error_strip_remote_error (error); g_warning ("A backend call failed: %s", error->message); + g_clear_error (&error); } should_close_session = !request->exported || response != 0; if (request->exported) { - if (response != 0) + if (!results) { - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - results = g_variant_builder_end (&results_builder); + results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); } if (should_close_session) - session_close (session, TRUE); + xdp_session_close (session, TRUE); } static XdpOptionKey input_capture_set_pointer_barriers_options[] = { @@ -507,17 +511,18 @@ handle_set_pointer_barriers (XdpDbusInputCapture *object, GVariant *arg_barriers, uint32_t arg_zone_set) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(XdpDbusImplRequest) impl_request = NULL; InputCaptureSession *input_capture_session; g_autoptr(GError) error = NULL; - GVariantBuilder options_builder; - GVariant *options; - Session *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; + XdpSession *session; REQUEST_AUTOLOCK (request); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -529,7 +534,7 @@ handle_set_pointer_barriers (XdpDbusInputCapture *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -538,7 +543,7 @@ handle_set_pointer_barriers (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - input_capture_session = (InputCaptureSession *)session; + input_capture_session = INPUT_CAPTURE_SESSION (session); switch (input_capture_session->state) { @@ -567,10 +572,9 @@ handle_set_pointer_barriers (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, input_capture_set_pointer_barriers_options, G_N_ELEMENTS (input_capture_set_pointer_barriers_options), @@ -580,7 +584,7 @@ handle_set_pointer_barriers (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -612,13 +616,14 @@ handle_enable (XdpDbusInputCapture *object, const char *arg_session_handle, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; InputCaptureSession *input_capture_session; g_autoptr(GError) error = NULL; - GVariantBuilder options_builder; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -630,7 +635,7 @@ handle_enable (XdpDbusInputCapture *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -639,7 +644,7 @@ handle_enable (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - input_capture_session = (InputCaptureSession *)session; + input_capture_session = INPUT_CAPTURE_SESSION (session); switch (input_capture_session->state) { @@ -661,8 +666,6 @@ handle_enable (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); - if (!xdp_filter_options (arg_options, &options_builder, input_capture_enable_options, G_N_ELEMENTS (input_capture_enable_options), @@ -694,7 +697,7 @@ handle_enable (XdpDbusInputCapture *object, xdp_dbus_impl_input_capture_call_enable (impl, arg_session_handle, xdp_app_info_get_id (call->app_info), - g_variant_builder_end (&options_builder), + g_variant_builder_end (&options_builder), NULL, NULL, NULL); @@ -713,13 +716,14 @@ handle_disable (XdpDbusInputCapture *object, const char *arg_session_handle, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; InputCaptureSession *input_capture_session; g_autoptr(GError) error = NULL; - GVariantBuilder options_builder; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -731,7 +735,7 @@ handle_disable (XdpDbusInputCapture *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -740,7 +744,7 @@ handle_disable (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - input_capture_session = (InputCaptureSession *)session; + input_capture_session = INPUT_CAPTURE_SESSION (session); switch (input_capture_session->state) { @@ -762,7 +766,6 @@ handle_disable (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, input_capture_disable_options, G_N_ELEMENTS (input_capture_disable_options), @@ -814,13 +817,14 @@ handle_release (XdpDbusInputCapture *object, const char *arg_session_handle, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; InputCaptureSession *input_capture_session; g_autoptr(GError) error = NULL; - GVariantBuilder options_builder; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -832,7 +836,7 @@ handle_release (XdpDbusInputCapture *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -841,7 +845,7 @@ handle_release (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - input_capture_session = (InputCaptureSession *)session; + input_capture_session = INPUT_CAPTURE_SESSION (session); switch (input_capture_session->state) { @@ -863,7 +867,6 @@ handle_release (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, input_capture_release_options, G_N_ELEMENTS (input_capture_release_options), @@ -912,15 +915,16 @@ handle_connect_to_eis (XdpDbusInputCapture *object, const char *arg_session_handle, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; InputCaptureSession *input_capture_session; g_autoptr(GUnixFDList) out_fd_list = NULL; g_autoptr(GError) error = NULL; - GVariantBuilder empty; + g_auto(GVariantBuilder) empty = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); GVariant *fd; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -932,7 +936,7 @@ handle_connect_to_eis (XdpDbusInputCapture *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -941,7 +945,7 @@ handle_connect_to_eis (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - input_capture_session = (InputCaptureSession *)session; + input_capture_session = INPUT_CAPTURE_SESSION (session); switch (input_capture_session->state) { @@ -963,8 +967,6 @@ handle_connect_to_eis (XdpDbusInputCapture *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&empty, G_VARIANT_TYPE_VARDICT); - if (!xdp_dbus_impl_input_capture_call_connect_to_eis_sync (impl, arg_session_handle, xdp_app_info_get_id (call->app_info), @@ -1005,7 +1007,7 @@ pass_signal (XdpDbusImplInputCapture *impl, gpointer data) { GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); - g_autoptr(Session) session = lookup_session (session_id); + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); g_dbus_connection_emit_signal (connection, session->sender, @@ -1022,10 +1024,10 @@ on_disabled_cb (XdpDbusImplInputCapture *impl, GVariant *options, gpointer data) { - g_autoptr(Session) session = lookup_session (session_id); + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); InputCaptureSession *input_capture_session; - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_critical ("Invalid session type for signal"); return; @@ -1055,10 +1057,10 @@ on_activated_cb (XdpDbusImplInputCapture *impl, GVariant *options, gpointer data) { - g_autoptr(Session) session = lookup_session (session_id); + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); InputCaptureSession *input_capture_session; - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_critical ("Invalid session type for signal"); return; @@ -1087,10 +1089,10 @@ on_deactivated_cb (XdpDbusImplInputCapture *impl, GVariant *options, gpointer data) { - g_autoptr(Session) session = lookup_session (session_id); + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); InputCaptureSession *input_capture_session; - if (!is_input_capture_session (session)) + if (!IS_INPUT_CAPTURE_SESSION (session)) { g_critical ("Invalid session type for signal"); return; @@ -1105,7 +1107,7 @@ on_deactivated_cb (XdpDbusImplInputCapture *impl, break; case INPUT_CAPTURE_SESSION_STATE_ACTIVE: pass_signal (impl, "Deactivated", session_id, options, data); - input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_ACTIVE; + input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_ENABLED; break; case INPUT_CAPTURE_SESSION_STATE_DISABLED: case INPUT_CAPTURE_SESSION_STATE_CLOSED: @@ -1113,6 +1115,36 @@ on_deactivated_cb (XdpDbusImplInputCapture *impl, } } +static void +on_zones_changed_cb (XdpDbusImplInputCapture *impl, + const char *session_id, + GVariant *options, + gpointer data) +{ + g_autoptr(XdpSession) session = xdp_session_lookup (session_id); + InputCaptureSession *input_capture_session; + + if (!IS_INPUT_CAPTURE_SESSION (session)) + { + g_critical ("Invalid session type for signal"); + return; + } + + input_capture_session = (InputCaptureSession*)session; + + switch (input_capture_session->state) + { + case INPUT_CAPTURE_SESSION_STATE_INIT: + case INPUT_CAPTURE_SESSION_STATE_ENABLED: + case INPUT_CAPTURE_SESSION_STATE_ACTIVE: + case INPUT_CAPTURE_SESSION_STATE_DISABLED: + pass_signal (impl, "ZonesChanged", session_id, options, data); + break; + case INPUT_CAPTURE_SESSION_STATE_CLOSED: + break; + } +} + static void input_capture_init (InputCapture *input_capture) { @@ -1128,6 +1160,7 @@ input_capture_init (InputCapture *input_capture) g_signal_connect (impl, "disabled", G_CALLBACK (on_disabled_cb), input_capture); g_signal_connect (impl, "activated", G_CALLBACK (on_activated_cb), input_capture); g_signal_connect (impl, "deactivated", G_CALLBACK (on_deactivated_cb), input_capture); + g_signal_connect (impl, "zones-changed", G_CALLBACK (on_zones_changed_cb), input_capture); } static void @@ -1138,9 +1171,9 @@ input_capture_class_init (InputCaptureClass *klass) } static void -input_capture_session_close (Session *session) +input_capture_session_close (XdpSession *session) { - InputCaptureSession *input_capture_session = (InputCaptureSession *)session; + InputCaptureSession *input_capture_session = INPUT_CAPTURE_SESSION (session); input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_CLOSED; @@ -1162,12 +1195,12 @@ static void input_capture_session_class_init (InputCaptureSessionClass *klass) { GObjectClass *object_class; - SessionClass *session_class; + XdpSessionClass *session_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = input_capture_session_finalize; - session_class = (SessionClass *)klass; + session_class = (XdpSessionClass *)klass; session_class->close = input_capture_session_close; } diff --git a/src/input-capture.h b/src/input-capture.h index 7db7a7b..f2bae7b 100644 --- a/src/input-capture.h +++ b/src/input-capture.h @@ -1,10 +1,12 @@ /* * Copyright © 2022 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/location.c b/src/location.c index 9a91a72..6eee02e 100644 --- a/src/location.c +++ b/src/location.c @@ -1,10 +1,12 @@ /* * Copyright © 2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -28,11 +30,11 @@ #include #include "location.h" -#include "request.h" -#include "permissions.h" +#include "xdp-request.h" +#include "xdp-permissions.h" #include "xdp-dbus.h" #include "xdp-utils.h" -#include "session.h" +#include "xdp-session.h" #include "geoclue-dbus.h" #include @@ -51,7 +53,7 @@ typedef enum { typedef struct { - Session parent; + XdpSession parent; LocationSessionState state; @@ -64,12 +66,24 @@ typedef struct typedef struct { - SessionClass parent_class; + XdpSessionClass parent_class; } LocationSessionClass; GType location_session_get_type (void); -G_DEFINE_TYPE (LocationSession, location_session, session_get_type ()) +G_DEFINE_TYPE (LocationSession, location_session, xdp_session_get_type ()) + +G_GNUC_UNUSED static inline LocationSession * +LOCATION_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_CAST (ptr, location_session_get_type (), LocationSession); +} + +G_GNUC_UNUSED static inline gboolean +IS_LOCATION_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_TYPE (ptr, location_session_get_type ()); +} static void location_session_init (LocationSession *session) @@ -80,9 +94,9 @@ location_session_init (LocationSession *session) } static void -location_session_close (Session *session) +location_session_close (XdpSession *session) { - LocationSession *loc_session = (LocationSession *)session; + LocationSession *loc_session = LOCATION_SESSION (session); loc_session->state = LOCATION_SESSION_STATE_CLOSED; @@ -95,7 +109,7 @@ location_session_close (Session *session) static void location_session_finalize (GObject *object) { - LocationSession *loc_session = (LocationSession *)object; + LocationSession *loc_session = LOCATION_SESSION (object); g_clear_object (&loc_session->client); @@ -106,7 +120,7 @@ static void location_session_class_init (LocationSessionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - SessionClass *session_class = (SessionClass *)klass; + XdpSessionClass *session_class = (XdpSessionClass *)klass; object_class->finalize = location_session_finalize; @@ -120,8 +134,8 @@ location_session_new (GVariant *options, { GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); const gchar *sender = g_dbus_method_invocation_get_sender (invocation); - XdpAppInfo *app_info = xdp_invocation_lookup_app_info_sync (invocation, NULL, NULL); - Session *session; + XdpAppInfo *app_info = xdp_invocation_ensure_app_info_sync (invocation, NULL, NULL); + XdpSession *session; session = g_initable_new (location_session_get_type (), NULL, error, "sender", sender, @@ -144,7 +158,7 @@ location_updated (GeoclueClient *client, const char *new_location, gpointer data) { - Session *session = data; + XdpSession *session = data; g_autoptr(GVariant) ret = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) dict = NULL; @@ -229,9 +243,9 @@ location_session_start (LocationSession *loc_session) return FALSE; } - g_debug ("location session '%s', GeoClue client '%s'", ((Session*)loc_session)->id, client_id); + g_debug ("location session '%s', GeoClue client '%s'", ((XdpSession*)loc_session)->id, client_id); g_debug ("location session '%s', distance-threshold %d, time-threshold %d, accuracy %s", - ((Session *)loc_session)->id, + XDP_SESSION (loc_session)->id, loc_session->distance_threshold, loc_session->time_threshold, gclue_accuracy_level_to_string (loc_session->accuracy)); @@ -257,7 +271,7 @@ location_session_start (LocationSession *loc_session) g_debug ("GeoClue client '%s' started", client_id); loc_session->state = LOCATION_SESSION_STATE_STARTED; - g_debug ("location session '%s' started", ((Session*)loc_session)->id); + g_debug ("location session '%s' started", ((XdpSession*)loc_session)->id); return TRUE; } @@ -339,7 +353,7 @@ get_location_permissions (XdpAppInfo *app_info, g_debug ("Getting location permissions for '%s'", app_id); - perms = get_permissions_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); + perms = xdp_get_permissions_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); if (perms == NULL) return FALSE; @@ -376,7 +390,7 @@ set_location_permissions (const char *app_id, g_debug ("set permission store accuracy: %d -> %s", accuracy, permissions[0]); - set_permissions_sync (app_id, PERMISSION_TABLE, PERMISSION_ID, permissions); + xdp_set_permissions_sync (app_id, PERMISSION_TABLE, PERMISSION_ID, permissions); } /*** Location boilerplace ***/ @@ -409,7 +423,8 @@ handle_create_session (XdpDbusLocation *object, GVariant *arg_options) { g_autoptr(GError) error = NULL; - LocationSession *session; + LocationSession *loc_session; + XdpSession *session; guint threshold; guint accuracy; @@ -423,31 +438,33 @@ handle_create_session (XdpDbusLocation *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - session = location_session_new (arg_options, invocation, &error); - if (!session) + loc_session = location_session_new (arg_options, invocation, &error); + if (!loc_session) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } + session = XDP_SESSION (loc_session); + if (g_variant_lookup (arg_options, "distance-threshold", "u", &threshold)) - session->distance_threshold = threshold; + loc_session->distance_threshold = threshold; if (g_variant_lookup (arg_options, "time-threshold", "u", &threshold)) - session->time_threshold = threshold; + loc_session->time_threshold = threshold; if (g_variant_lookup (arg_options, "accuracy", "u", &accuracy)) { if (accuracy == 0) - session->accuracy = GCLUE_ACCURACY_LEVEL_NONE; + loc_session->accuracy = GCLUE_ACCURACY_LEVEL_NONE; else if (accuracy == 1) - session->accuracy = GCLUE_ACCURACY_LEVEL_COUNTRY; + loc_session->accuracy = GCLUE_ACCURACY_LEVEL_COUNTRY; else if (accuracy == 2) - session->accuracy = GCLUE_ACCURACY_LEVEL_CITY; + loc_session->accuracy = GCLUE_ACCURACY_LEVEL_CITY; else if (accuracy == 3) - session->accuracy = GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD; + loc_session->accuracy = GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD; else if (accuracy == 4) - session->accuracy = GCLUE_ACCURACY_LEVEL_STREET; + loc_session->accuracy = GCLUE_ACCURACY_LEVEL_STREET; else if (accuracy == 5) - session->accuracy = GCLUE_ACCURACY_LEVEL_EXACT; + loc_session->accuracy = GCLUE_ACCURACY_LEVEL_EXACT; else { g_dbus_method_invocation_return_error (invocation, @@ -458,18 +475,18 @@ handle_create_session (XdpDbusLocation *object, } } - if (!session_export ((Session *)session, &error)) + if (!xdp_session_export (session, &error)) { g_warning ("Failed to export session: %s", error->message); - session_close ((Session *)session, FALSE); + xdp_session_close (session, FALSE); } else { - g_debug ("CreateSession new session '%s'", ((Session *)session)->id); - session_register ((Session *)session); + g_debug ("CreateSession new session '%s'", session->id); + xdp_session_register (session); } - xdp_dbus_location_complete_create_session (object, invocation, ((Session *)session)->id); + xdp_dbus_location_complete_create_session (object, invocation, session->id); return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -482,13 +499,13 @@ handle_start_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; + XdpRequest *request = XDP_REQUEST (task_data); const char *parent_window; const char *id; gint64 last_used = 0; g_autoptr(GError) error = NULL; guint response = 2; - Session *session; + XdpSession *session; LocationSession *loc_session; GClueAccuracyLevel accuracy; @@ -497,7 +514,7 @@ handle_start_in_thread_func (GTask *task, session = g_object_get_qdata (G_OBJECT (request), quark_request_session); SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); - loc_session = (LocationSession *)session; + loc_session = LOCATION_SESSION (session); parent_window = (const char *)g_object_get_data (G_OBJECT (request), "parent-window"); @@ -508,7 +525,8 @@ handle_start_in_thread_func (GTask *task, guint access_response = 2; g_autoptr(GVariant) access_results = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder access_opt_builder; + g_auto(GVariantBuilder) access_opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autofree char *app_id = NULL; g_autofree char *title = NULL; g_autofree char *subtitle = NULL; @@ -520,9 +538,8 @@ handle_start_in_thread_func (GTask *task, request->id, NULL, NULL); - request_set_impl_request (request, impl_request); + xdp_request_set_impl_request (request, impl_request); - g_variant_builder_init (&access_opt_builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&access_opt_builder, "{sv}", "deny_label", g_variant_new_string (_("Deny Access"))); g_variant_builder_add (&access_opt_builder, "{sv}", @@ -532,11 +549,9 @@ handle_start_in_thread_func (GTask *task, if (g_strcmp0 (id, "") != 0) { - g_autoptr(GAppInfo) info = NULL; + GAppInfo *info = xdp_app_info_get_gappinfo (request->app_info); const gchar *name = NULL; - info = xdp_app_info_load_app_info (request->app_info); - if (info) { name = g_app_info_get_display_name (G_APP_INFO (info)); @@ -585,7 +600,7 @@ handle_start_in_thread_func (GTask *task, goto out; } - request_set_impl_request (request, NULL); + xdp_request_set_impl_request (request, NULL); accuracy = (access_response == 0) ? GCLUE_ACCURACY_LEVEL_EXACT : GCLUE_ACCURACY_LEVEL_NONE; } @@ -609,7 +624,7 @@ handle_start_in_thread_func (GTask *task, loc_session->accuracy = accuracy; } - if (location_session_start ((LocationSession*)session)) + if (location_session_start (loc_session)) response = 0; else response = 2; @@ -617,20 +632,20 @@ handle_start_in_thread_func (GTask *task, out: if (request->exported) { - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_debug ("sending response: %d", response); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&opt_builder)); - request_unexport (request); + xdp_request_unexport (request); } if (response != 0) { g_debug ("closing session"); - session_close ((Session *)session, FALSE); + xdp_session_close (session, FALSE); } } @@ -641,8 +656,8 @@ handle_start (XdpDbusLocation *object, const char *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); - Session *session; + XdpRequest *request = xdp_request_from_invocation (invocation); + XdpSession *session; LocationSession *loc_session; g_autoptr(GTask) task = NULL; @@ -658,7 +673,7 @@ handle_start (XdpDbusLocation *object, REQUEST_AUTOLOCK (request); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -670,7 +685,7 @@ handle_start (XdpDbusLocation *object, SESSION_AUTOLOCK_UNREF (session); - loc_session = (LocationSession *)session; + loc_session = LOCATION_SESSION (session); switch (loc_session->state) { case LOCATION_SESSION_STATE_INIT: @@ -690,7 +705,7 @@ handle_start (XdpDbusLocation *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); g_object_set_data_full (G_OBJECT (request), "parent-window", g_strdup (arg_parent_window), g_free); diff --git a/src/location.h b/src/location.h index 16a5f7a..74c8a10 100644 --- a/src/location.h +++ b/src/location.h @@ -1,10 +1,12 @@ /* * Copyright © 2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/memory-monitor.c b/src/memory-monitor.c index 8808e78..48fc9d1 100644 --- a/src/memory-monitor.c +++ b/src/memory-monitor.c @@ -1,10 +1,12 @@ /* * Copyright © 2016, 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -25,7 +27,6 @@ #include #include "memory-monitor.h" -#include "request.h" #include "xdp-dbus.h" #include "xdp-utils.h" diff --git a/src/memory-monitor.h b/src/memory-monitor.h index c8e742a..3daba4c 100644 --- a/src/memory-monitor.h +++ b/src/memory-monitor.h @@ -1,10 +1,12 @@ /* * Copyright © 2016, 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/meson.build b/src/meson.build index ab3a0d1..abf856f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -6,7 +6,13 @@ portal_built_sources = gnome.gdbus_codegen( interface_prefix: 'org.freedesktop.portal', namespace: 'XdpDbus', docbook: 'portal', - autocleanup: 'none', +) +host_built_sources = gnome.gdbus_codegen( + 'xdp-host-dbus', + sources: portal_host_sources, + interface_prefix: 'org.freedesktop.host.portal', + namespace: 'XdpDbusHost', + docbook: 'portal', ) impl_built_sources = gnome.gdbus_codegen( 'xdp-impl-dbus', @@ -14,15 +20,13 @@ impl_built_sources = gnome.gdbus_codegen( interface_prefix: 'org.freedesktop.impl.portal', namespace: 'XdpDbusImpl', docbook: 'portal', - autocleanup: 'none', ) -background_monitor_sources = gnome.gdbus_codegen( +background_monitor_built_sources = gnome.gdbus_codegen( 'xdp-background-dbus', sources: background_monitor_sources, interface_prefix: 'org.freedesktop.background', namespace: 'XdpDbusBackground', docbook: 'portal', - autocleanup: 'all', ) if have_geoclue geoclue_built_sources = gnome.gdbus_codegen( @@ -30,13 +34,19 @@ if have_geoclue sources: 'org.freedesktop.GeoClue2.Client.xml', interface_prefix: 'org.freedesktop.GeoClue2', namespace: 'Geoclue', - autocleanup: 'none', ) else geoclue_built_sources = [] endif -built_resources = gnome.compile_resources( +enums_sources = gnome.mkenums_simple( + 'xdp-enum-types', + sources: files( + 'xdp-app-info-private.h', + ), +) + +built_resources = enums_sources + gnome.compile_resources( 'xdg-desktop-resources', 'xdg-desktop-portal.gresource.xml', c_name: '_xdg_desktop', @@ -45,21 +55,42 @@ built_resources = gnome.compile_resources( sd_escape_sources = files('sd-escape.c') +xdp_method_info_built_sources = configure_file( + command: [find_program('generate-method-info.py'), portal_sources], + capture: true, + output: 'xdp-method-info-generated.c', +) + +xdp_method_info_sources = files('xdp-method-info.c') + xdp_method_info_built_sources + +xdp_utils_deps = [] +xdp_utils_includes = include_directories('.') +xdp_utils_sources = enums_sources + files( + 'xdp-utils.c', + 'xdp-app-info.c', + 'xdp-sealed-fd.c', + 'xdp-app-info-host.c', + 'xdp-app-info-flatpak.c', + 'xdp-app-info-snap.c', + 'xdp-app-info-test.c', + 'xdp-usb-query.c', +) + +if have_libsystemd + xdp_utils_sources += sd_escape_sources + xdp_utils_deps += [libsystemd_dep] +endif + xdg_desktop_portal_sources = files( 'account.c', 'background.c', - 'background-monitor.c', - 'call.c', 'camera.c', 'clipboard.c', - 'device.c', - 'documents.c', 'dynamic-launcher.c', 'email.c', 'file-chooser.c', 'flatpak-instance.c', 'gamemode.c', - 'glib-backports.c', 'global-shortcuts.c', 'inhibit.c', 'input-capture.c', @@ -67,31 +98,38 @@ xdg_desktop_portal_sources = files( 'network-monitor.c', 'notification.c', 'open-uri.c', - 'permissions.c', 'pipewire.c', - 'portal-impl.c', 'power-profile-monitor.c', 'print.c', 'proxy-resolver.c', 'realtime.c', + 'registry.c', 'remote-desktop.c', - 'request.c', - 'restore-token.c', 'screen-cast.c', 'screenshot.c', 'secret.c', - 'session.c', 'settings.c', 'trash.c', 'wallpaper.c', 'xdg-desktop-portal.c', - 'xdp-utils.c', + 'xdp-app-launch-context.c', + 'xdp-background-monitor.c', + 'xdp-call.c', + 'xdp-documents.c', + 'xdp-permissions.c', + 'xdp-portal-impl.c', + 'xdp-request.c', + 'xdp-session.c', + 'xdp-session-persistence.c', ) xdg_desktop_portal_sources += [ + xdp_utils_sources, + xdp_method_info_sources, portal_built_sources, + host_built_sources, impl_built_sources, - background_monitor_sources, + background_monitor_built_sources, geoclue_built_sources, built_resources, ] @@ -102,8 +140,10 @@ if have_geoclue ) endif -if have_libsystemd - xdg_desktop_portal_sources += sd_escape_sources +if have_gudev + xdg_desktop_portal_sources += files( + 'usb.c', + ) endif common_deps = [ @@ -113,10 +153,14 @@ common_deps = [ json_glib_dep, ] +if gudev_dep.found() + common_deps += gudev_dep +endif + xdg_desktop_portal_deps = common_deps + [ geoclue_dep, pipewire_dep, - libsystemd_dep, + xdp_utils_deps, ] incs_xdg_desktop_portal = [ @@ -134,9 +178,6 @@ xdg_desktop_portal = executable( install_dir: libexecdir, ) -xdp_utils_includes = include_directories('.') -xdp_utils_sources = files('xdp-utils.c') - configure_file( input: 'xdg-desktop-portal.service.in', output: '@BASENAME@', @@ -158,15 +199,29 @@ if bwrap.found() validate_icon_c_args += '-DHELPER="@0@"'.format(bwrap.full_path()) endif -executable( +xdp_validate_icon = executable( 'xdg-desktop-portal-validate-icon', 'validate-icon.c', - dependencies: [gdk_pixbuf_dep], + dependencies: [gdk_pixbuf_dep, gio_unix_dep], c_args: validate_icon_c_args, install: true, install_dir: libexecdir, ) +validate_sound_c_args = ['-D_GNU_SOURCE=1'] +if bwrap.found() + validate_sound_c_args += '-DHELPER="@0@"'.format(bwrap.full_path()) +endif + +xdp_validate_sound = executable( + 'xdg-desktop-portal-validate-sound', + 'validate-sound.c', + dependencies: [gst_pbutils_dep], + c_args: validate_sound_c_args, + install: true, + install_dir: libexecdir, +) + configure_file( input: 'xdg-desktop-portal-rewrite-launchers.service.in', output: '@BASENAME@', @@ -178,7 +233,6 @@ configure_file( executable( 'xdg-desktop-portal-rewrite-launchers', 'rewrite-launchers.c', - 'glib-backports.c', dependencies: common_deps, include_directories: common_includes, install: true, diff --git a/src/network-monitor.c b/src/network-monitor.c index 6e5b526..796e165 100644 --- a/src/network-monitor.c +++ b/src/network-monitor.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,7 +26,8 @@ #include #include "network-monitor.h" -#include "request.h" +#include "xdp-call.h" +#include "xdp-app-info.h" #include "xdp-dbus.h" #include "xdp-utils.h" @@ -57,9 +60,9 @@ static gboolean handle_get_available (XdpDbusNetworkMonitor *object, GDBusMethodInvocation *invocation) { - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); - if (!xdp_app_info_has_network (request->app_info)) + if (!xdp_app_info_has_network (call->app_info)) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -81,9 +84,9 @@ static gboolean handle_get_metered (XdpDbusNetworkMonitor *object, GDBusMethodInvocation *invocation) { - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); - if (!xdp_app_info_has_network (request->app_info)) + if (!xdp_app_info_has_network (call->app_info)) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -105,9 +108,9 @@ static gboolean handle_get_connectivity (XdpDbusNetworkMonitor *object, GDBusMethodInvocation *invocation) { - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); - if (!xdp_app_info_has_network (request->app_info)) + if (!xdp_app_info_has_network (call->app_info)) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -129,9 +132,9 @@ static gboolean handle_get_status (XdpDbusNetworkMonitor *object, GDBusMethodInvocation *invocation) { - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); - if (!xdp_app_info_has_network (request->app_info)) + if (!xdp_app_info_has_network (call->app_info)) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -141,11 +144,11 @@ handle_get_status (XdpDbusNetworkMonitor *object, else { NetworkMonitor *nm = (NetworkMonitor *)object; - GVariantBuilder status; + g_auto(GVariantBuilder) status = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); gboolean b; guint c; - g_variant_builder_init (&status, G_VARIANT_TYPE_VARDICT); b = g_network_monitor_get_network_available (nm->monitor); g_variant_builder_add (&status, "{sv}", "available", g_variant_new_boolean (b)); @@ -181,9 +184,9 @@ handle_can_reach (XdpDbusNetworkMonitor *object, const char *hostname, guint port) { - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); - if (!xdp_app_info_has_network (request->app_info)) + if (!xdp_app_info_has_network (call->app_info)) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, diff --git a/src/network-monitor.h b/src/network-monitor.h index 551f487..eb496e4 100644 --- a/src/network-monitor.h +++ b/src/network-monitor.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/notification.c b/src/notification.c index 8cb3796..0dc6db4 100644 --- a/src/notification.c +++ b/src/notification.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -23,11 +25,14 @@ #include #include #include +#include #include #include "notification.h" -#include "request.h" -#include "permissions.h" +#include "xdp-call.h" +#include "xdp-permissions.h" +#include "xdp-request.h" +#include "xdp-app-info.h" #include "xdp-dbus.h" #include "xdp-utils.h" @@ -49,6 +54,7 @@ struct _NotificationClass static XdpDbusImplNotification *impl; static Notification *notification; +static guint32 impl_version; G_LOCK_DEFINE (active); static GHashTable *active; @@ -97,6 +103,75 @@ pair_copy (Pair *o) return p; } +struct _CallData { + GObject parent_instance; + GDBusMethodInvocation *inv; + XdpAppInfo *app_info; + GMutex mutex; + + char *sender; + char *id; + GVariant *notification; + GUnixFDList *in_fd_list; + GUnixFDList *out_fd_list; +}; + +G_DECLARE_FINAL_TYPE (CallData, call_data, CALL, DATA, GObject) +G_DEFINE_TYPE (CallData, call_data, G_TYPE_OBJECT); +#define CALL_DATA_AUTOLOCK(call_data) G_GNUC_UNUSED __attribute__((cleanup (auto_unlock_helper))) GMutex * G_PASTE (request_auto_unlock, __LINE__) = auto_lock_helper (&call_data->mutex); + +static void +call_data_init (CallData *call_data) +{ + g_mutex_init (&call_data->mutex); +} + +static CallData * +call_data_new (GDBusMethodInvocation *inv, + XdpAppInfo *app_info, + const char *sender, + const char *id, + GVariant *notification, + GUnixFDList *in_fd_list) +{ + CallData *call_data = g_object_new (call_data_get_type(), NULL); + + call_data->inv = g_object_ref (inv); + call_data->app_info = g_object_ref (app_info); + call_data->sender = g_strdup (sender); + call_data->id = g_strdup (id); + if (notification) + call_data->notification = g_variant_ref (notification); + g_set_object (&call_data->in_fd_list, in_fd_list); + call_data->out_fd_list = g_unix_fd_list_new (); + + return call_data; +} + +static void +call_data_finalize (GObject *object) +{ + CallData *call_data = CALL_DATA (object); + + g_clear_object (&call_data->inv); + g_clear_object (&call_data->app_info); + g_clear_pointer (&call_data->id, g_free); + g_clear_pointer (&call_data->sender, g_free); + g_clear_pointer (&call_data->notification, g_variant_unref); + g_clear_object (&call_data->in_fd_list); + g_clear_object (&call_data->out_fd_list); + + G_OBJECT_CLASS (call_data_parent_class)->finalize (object); +} + +static void +call_data_class_init (CallDataClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = call_data_finalize; +} + GType notification_get_type (void) G_GNUC_CONST; static void notification_iface_init (XdpDbusNotificationIface *iface); @@ -110,10 +185,10 @@ add_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(CallData) call_data = data; g_autoptr(GError) error = NULL; - if (!xdp_dbus_impl_notification_call_add_notification_finish (impl, result, &error)) + if (!xdp_dbus_impl_notification_call_add_notification_finish (impl, NULL, result, &error)) { g_dbus_error_strip_remote_error (error); g_warning ("Backend call failed: %s", error->message); @@ -122,11 +197,11 @@ add_done (GObject *source, { Pair p; - p.app_id = (char *)xdp_app_info_get_id (request->app_info); - p.id = (char *)g_object_get_data (G_OBJECT (request), "id"); + p.app_id = (char *)xdp_app_info_get_id (call_data->app_info); + p.id = call_data->id; G_LOCK (active); - g_hash_table_insert (active, pair_copy (&p), g_strdup (request->sender)); + g_hash_table_insert (active, pair_copy (&p), g_strdup (call_data->sender)); G_UNLOCK (active); } } @@ -134,18 +209,18 @@ add_done (GObject *source, static gboolean get_notification_allowed (const char *app_id) { - Permission permission; + XdpPermission permission; - permission = get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); + permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); - if (permission == PERMISSION_NO) + if (permission == XDP_PERMISSION_NO) return FALSE; - if (permission == PERMISSION_UNSET) + if (permission == XDP_PERMISSION_UNSET) { g_debug ("No notification permissions stored for %s: allowing", app_id); - set_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID, PERMISSION_YES); + xdp_set_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID, XDP_PERMISSION_YES); } return TRUE; @@ -169,34 +244,234 @@ check_value_type (const char *key, return FALSE; } +static void +markup_parser_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + GString *composed = user_data; + + while (text_len > 0) + { + gsize len = MIN (text_len, G_MAXSSIZE); + g_autofree char *escaped = g_markup_escape_text (text, len); + + g_string_append (composed, escaped); + text_len -= len; + text += len; + } +} + +static void +markup_parser_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + GString *composed = user_data; + + if (strcmp (element_name, "b") == 0) + { + g_string_append_len (composed, "", -1); + } + else if (strcmp (element_name, "i") == 0) + { + g_string_append_len (composed, "", -1); + } + else if (strcmp (element_name, "a") == 0) + { + int i; + + for (i = 0; attribute_names[i]; i++) + { + if (strcmp (attribute_names[i], "href") == 0) + { + g_autofree char *escaped = g_markup_escape_text (attribute_values[i], -1); + + g_string_append_printf (composed, "", escaped); + break; + } + } + } +} + +static void +markup_parser_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + GString *composed = user_data; + + if (strcmp (element_name, "b") == 0) + g_string_append_len (composed, "", -1); + else if (strcmp (element_name, "i") == 0) + g_string_append_len (composed, "", -1); + else if (strcmp (element_name, "a") == 0) + g_string_append_len (composed, "", -1); +} + +static const GMarkupParser markup_parser = { + markup_parser_start_element, + markup_parser_end_element, + markup_parser_text, + NULL, + NULL, +}; + +static gchar * +strip_multiple_spaces (const gchar *text, + gsize length) +{ + GString *composed; + gchar *str = (gchar *) text; + + composed = g_string_sized_new (length); + + while (*str) + { + gunichar c = g_utf8_get_char (str); + gboolean needs_space = FALSE; + + while (g_unichar_isspace (c)) + { + needs_space = TRUE; + + str = g_utf8_next_char (str); + + if (!*str) + break; + + c = g_utf8_get_char (str); + } + + if (*str) + { + if (needs_space) + g_string_append_c (composed, ' '); + + g_string_append_unichar (composed, c); + str = g_utf8_next_char (str); + } + } + + return g_string_free (composed, FALSE); +} + + +static gboolean +parse_markup_body (GVariantBuilder *builder, + GVariant *body, + GError **error) +{ + g_autoptr(GMarkupParseContext) context = NULL; + g_autoptr(GString) composed = NULL; + const gchar* text = NULL; + gsize text_length = 0; + + if (!check_value_type ("markup-body", body, G_VARIANT_TYPE_STRING, error)) + return FALSE; + + text = g_variant_get_string (body, &text_length); + composed = g_string_sized_new (text_length); + context = g_markup_parse_context_new (&markup_parser, 0, composed, NULL); + + /* The markup parser expects the markup to start with an element, therefore add one*/ + if (g_markup_parse_context_parse (context, "", -1, error) && + g_markup_parse_context_parse (context, text, -1, error) && + g_markup_parse_context_parse (context, "", -1, error) && + g_markup_parse_context_end_parse (context, error)) + { + gchar *stripped; + + stripped = strip_multiple_spaces (composed->str, composed->len); + g_variant_builder_add (builder, "{sv}", "markup-body", g_variant_new_take_string (stripped)); + + return TRUE; + } + else + { + g_prefix_error (error, "invalid markup-body: "); + return FALSE; + } +} + static gboolean -check_priority (GVariant *value, - GError **error) +parse_priority (GVariantBuilder *builder, + GVariant *value, + GError **error) { const char *priorities[] = { "low", "normal", "high", "urgent", NULL }; + const char *priority; if (!check_value_type ("priority", value, G_VARIANT_TYPE_STRING, error)) return FALSE; - if (!g_strv_contains (priorities, g_variant_get_string (value, NULL))) + priority = g_variant_get_string (value, NULL); + + if (!g_strv_contains (priorities, priority)) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "%s not a priority", priority); + return FALSE; + } + + g_variant_builder_add (builder, "{sv}", "priority", value); + + return TRUE; +} + +static gboolean +check_button_purpose (GVariant *value, + GError **error) +{ + const char *purpose; + const char *supported_purposes[] = { + "system.custom-alert", + "im.reply-with-text", + "call.accept", + "call.decline", + "call.hang-up", + "call.enable-speakerphone", + "call.disable-speakerphone", + NULL + }; + + if (!check_value_type ("purpose", value, G_VARIANT_TYPE_STRING, error)) + return FALSE; + + purpose = g_variant_get_string (value, NULL); + + if (!g_strv_contains (supported_purposes, purpose) && !g_str_has_prefix (purpose, "x-")) { g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "%s not a priority", g_variant_get_string (value, NULL)); + "%s is not a supported button purpose", purpose); return FALSE; } return TRUE; } + static gboolean -check_button (GVariant *button, - GError **error) +parse_button (GVariantBuilder *builder, + GVariant *button, + GError **error) { int i; - gboolean has_label = FALSE; - gboolean has_action = FALSE; + g_autoptr(GVariant) label = NULL; + g_autoptr(GVariant) action = NULL; + g_autoptr(GVariant) target = NULL; + g_autoptr(GVariant) purpose = NULL; + for (i = 0; i < g_variant_n_children (button); i++) { @@ -204,32 +479,52 @@ check_button (GVariant *button, g_autoptr(GVariant) value = NULL; g_variant_get_child (button, i, "{&sv}", &key, &value); + if (strcmp (key, "label") == 0) - has_label = TRUE; + { + if (!check_value_type (key, value, G_VARIANT_TYPE_STRING, error)) + return FALSE; + + if (!label) + label = g_steal_pointer (&value); + } else if (strcmp (key, "action") == 0) - has_action = TRUE; + { + if (!check_value_type (key, value, G_VARIANT_TYPE_STRING, error)) + return FALSE; + + if (!action) + action = g_steal_pointer (&value); + } else if (strcmp (key, "target") == 0) - ; + { + if (!target) + target = g_steal_pointer (&value); + } + else if (strcmp (key, "purpose") == 0 && impl_version > 1) + { + if (!check_button_purpose (value, error)) + return FALSE; + + if (!purpose) + purpose = g_steal_pointer (&value); + } else { - g_set_error (error, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "%s not valid key", key); - return FALSE; + g_debug ("Unsupported button property %s filtered from notification", key); } } - if (!has_label) + if (!label && !purpose) { g_set_error_literal (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "label key is missing"); + "label or purpose key is missing"); return FALSE; } - if (!has_action) + if (!action) { g_set_error_literal (error, XDG_DESKTOP_PORTAL_ERROR, @@ -238,61 +533,400 @@ check_button (GVariant *button, return FALSE; } + g_variant_builder_open (builder, G_VARIANT_TYPE ("a{sv}")); + + g_variant_builder_add (builder, "{sv}", "label", label); + g_variant_builder_add (builder, "{sv}", "action", action); + if (target) + g_variant_builder_add (builder, "{sv}", "target", target); + if (purpose) + g_variant_builder_add (builder, "{sv}", "purpose", purpose); + + g_variant_builder_close (builder); + return TRUE; } static gboolean -check_buttons (GVariant *value, - GError **error) +parse_buttons (GVariantBuilder *builder, + GVariant *value, + GError **error) { + gboolean result = TRUE; int i; if (!check_value_type ("buttons", value, G_VARIANT_TYPE ("aa{sv}"), error)) return FALSE; + g_variant_builder_open (builder, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (builder, "s", "buttons"); + g_variant_builder_open (builder, G_VARIANT_TYPE ("v")); + g_variant_builder_open (builder, G_VARIANT_TYPE ("aa{sv}")); + for (i = 0; i < g_variant_n_children (value); i++) { g_autoptr(GVariant) button = g_variant_get_child_value (value, i); - if (!check_button (button, error)) + if (!parse_button (builder, button, error)) { g_prefix_error (error, "invalid button: "); + result = FALSE; + break; + } + } + + g_variant_builder_close (builder); + g_variant_builder_close (builder); + g_variant_builder_close (builder); + + return result; +} + +static gboolean +parse_serialized_icon (GVariantBuilder *builder, + GVariant *icon, + GUnixFDList *in_fd_list, + GUnixFDList *out_fd_list, + GError **error) +{ + const char *key; + g_autoptr(GVariant) value = NULL; + + /* Since the specs allow a single string as icon name we need to keep support for it */ + if (g_variant_is_of_type (icon, G_VARIANT_TYPE_STRING)) + { + g_autoptr(GIcon) deserialized_icon = NULL; + + deserialized_icon = g_icon_deserialize (icon); + + if (G_IS_THEMED_ICON (deserialized_icon)) + { + g_autoptr(GVariant) serialized_icon = NULL; + + serialized_icon = g_icon_serialize (deserialized_icon); + + g_variant_builder_add (builder, "{sv}", "icon", serialized_icon); + return TRUE; + } + else + { + g_set_error_literal (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "only themed icons can be a string"); + return FALSE; + } + } + + if (!check_value_type ("icon", icon, G_VARIANT_TYPE("(sv)"), error)) + return FALSE; + + g_variant_get (icon, "(&sv)", &key, &value); + + /* This are the same keys as for serialized GIcons */ + if (strcmp (key, "themed") == 0) + { + if (!check_value_type (key, value, G_VARIANT_TYPE_STRING_ARRAY, error)) + return FALSE; + + g_variant_builder_add (builder, "{sv}", "icon", icon); + } + else if (strcmp (key, "bytes") == 0) + { + g_autoptr(GBytes) icon_bytes = NULL; + g_autoptr(GError) local_error = NULL; + g_autoptr(XdpSealedFd) sealed_icon = NULL; + + if (!check_value_type (key, value, G_VARIANT_TYPE_BYTESTRING, error)) + return FALSE; + + icon_bytes = g_variant_get_data_as_bytes (value); + sealed_icon = xdp_sealed_fd_new_from_bytes (icon_bytes, &local_error); + + if (!sealed_icon) + { + g_warning ("Failed to read icon: %s", local_error->message); + } + else if (xdp_validate_icon (sealed_icon, XDP_ICON_TYPE_NOTIFICATION, NULL, NULL)) + { + /* Since version 2 we only use file-descriptor icon */ + if (impl_version > 1) + { + g_autoptr(GVariant) fd_icon = NULL; + + fd_icon = xdp_sealed_fd_to_handle (sealed_icon, + out_fd_list, + &local_error); + + if (!fd_icon) + { + g_warning ("Failed to get create file-descriptor icon from bytes icon: %s", + local_error->message); + } + else + { + g_variant_builder_add (builder, "{sv}", "icon", fd_icon); + } + } + else + { + g_variant_builder_add (builder, "{sv}", "icon", icon); + } + } + } + else if (strcmp (key, "file-descriptor") == 0) + { + g_autoptr(GError) local_error = NULL; + g_autoptr(XdpSealedFd) sealed_icon = NULL; + + if (!check_value_type (key, value, G_VARIANT_TYPE_HANDLE, error)) + return FALSE; + + if (!(sealed_icon = xdp_sealed_fd_new_from_handle (value, + in_fd_list, + &local_error))) + { + g_warning ("Failed to seal fd: %s", local_error->message); + g_set_error_literal (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Invalid file descriptor: The file descriptor needs to be sealable"); return FALSE; } + + if (xdp_validate_icon (sealed_icon, XDP_ICON_TYPE_NOTIFICATION, NULL, NULL)) + { + /* Convert file descriptor icons to byte icons for backwards compatibility */ + if (impl_version < 2) + { + g_autoptr(GBytes) bytes = NULL; + GVariant *bytes_icon; + + bytes = xdp_sealed_fd_get_bytes (sealed_icon, &local_error); + if (!bytes) + { + g_warning ("Failed to get bytes from file-descriptor icon: %s", local_error->message); + return TRUE; + } + + bytes_icon = g_variant_new ("(sv)", "bytes", + g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, bytes, TRUE)); + + g_variant_builder_add (builder, "{sv}", "icon", bytes_icon); + } + else + { + g_autoptr(GVariant) fd_icon = NULL; + + fd_icon = xdp_sealed_fd_to_handle (sealed_icon, + out_fd_list, + &local_error); + if (!fd_icon) + { + g_warning ("Failed to get create file-descriptor icon from icon fd: %s", + local_error->message); + } + else + { + g_variant_builder_add (builder, "{sv}", "icon", fd_icon); + } + } + } } + else + { + g_debug ("Unsupported icon %s filtered from notification", key); + } + return TRUE; } static gboolean -check_serialized_icon (GVariant *value, - GError **error) +parse_serialized_sound (GVariantBuilder *builder, + GVariant *sound, + GUnixFDList *in_fd_list, + GUnixFDList *out_fd_list, + GError **error) { - g_autoptr(GIcon) icon = g_icon_deserialize (value); + const char *key; + g_autoptr(GVariant) value = NULL; - if (!icon) + if (g_variant_is_of_type (sound, G_VARIANT_TYPE_STRING)) { - g_set_error_literal (error, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "invalid icon"); - return FALSE; + key = g_variant_get_string (sound, NULL); + + if (strcmp (key, "silent") == 0 || strcmp (key, "default") == 0) + { + g_variant_builder_add (builder, "{sv}", "sound", sound); + return TRUE; + } + else + { + g_set_error_literal (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "invalid sound: invalid option"); + return FALSE; + } } - if (!G_IS_BYTES_ICON (icon) && !G_IS_THEMED_ICON (icon)) + if (!check_value_type ("sound", sound, G_VARIANT_TYPE("(sv)"), error)) + return FALSE; + + g_variant_get (sound, "(&sv)", &key, &value); + + if (strcmp (key, "file-descriptor") == 0) { - g_set_error_literal (error, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "invalid icon"); + g_autoptr(GError) local_error = NULL; + g_autoptr(XdpSealedFd) sealed_sound = NULL; + g_autoptr(GVariant) fd_sound = NULL; + + if (!check_value_type (key, value, G_VARIANT_TYPE_HANDLE, error)) + return FALSE; + + sealed_sound = xdp_sealed_fd_new_from_handle (value, + in_fd_list, + &local_error); + if (!sealed_sound) + { + g_warning ("Failed to seal fd: %s", local_error->message); + g_set_error_literal (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Invalid file descriptor: The file descriptor needs to be sealable"); + return FALSE; + } + + if (!xdp_validate_sound (sealed_sound)) + { + g_set_error_literal (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Invalid sound: The sound data is invalid"); + return FALSE; + } + + fd_sound = xdp_sealed_fd_to_handle (sealed_sound, + out_fd_list, + &local_error); + + if (!fd_sound) + { + g_warning ("Failed to get create file-descriptor icon from sound fd: %s", + local_error->message); + } + else + { + g_variant_builder_add (builder, "{sv}", "sound", fd_sound); + } + } + else + { + g_debug ("Unsupported sound %s filtered from notification", key); + } + + return TRUE; +} + +static gboolean +parse_display_hint (GVariantBuilder *builder, + GVariant *value, + GError **error) +{ + int i; + g_autofree const char **display_hints = NULL; + gsize display_hints_length; + const char *supported_display_hints[] = { + "transient", + "tray", + "persistent", + "hide-on-lock-screen", + "hide-content-on-lock-screen", + "show-as-new", + NULL + }; + + if (!check_value_type ("display-hint", value, G_VARIANT_TYPE_STRING_ARRAY, error)) + return FALSE; + + display_hints = g_variant_get_strv (value, &display_hints_length); + + if (display_hints_length == 0) + return TRUE; + + g_variant_builder_open (builder, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (builder, "s", "display-hint"); + g_variant_builder_open (builder, G_VARIANT_TYPE_VARIANT); + g_variant_builder_open (builder, G_VARIANT_TYPE_STRING_ARRAY); + + for (i = 0; display_hints[i]; i++) + { + if (!g_strv_contains (supported_display_hints, display_hints[i])) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "%s not a display-hint", display_hints[i]); + return FALSE; + } + + g_variant_builder_add (builder, "s", display_hints[i]); + } + + g_variant_builder_close (builder); + g_variant_builder_close (builder); + g_variant_builder_close (builder); + + return TRUE; +} + +static gboolean +parse_category (GVariantBuilder *builder, + GVariant *value, + GError **error) +{ + const char *category; + const char *supported_categories[] = { + "im.received", + "alarm.ringing", + "call.incoming", + "call.ongoing", + "call.unanswered", + "weather.warning.extreme", + "cellbroadcast.danger.extreme", + "cellbroadcast.danger.severe", + "cellbroadcast.amberalert", + "cellbroadcast.test", + "os.battery.low", + "browser.web-notification", + NULL + }; + + if (!check_value_type ("category", value, G_VARIANT_TYPE_STRING, error)) + return FALSE; + + category = g_variant_get_string (value, NULL); + + if (!g_strv_contains (supported_categories, category) && !g_str_has_prefix (category, "x-")) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "%s is not a supported category", category); return FALSE; } + g_variant_builder_add (builder, "{sv}", "category", value); + return TRUE; } static gboolean -check_notification (GVariant *notification, - GError **error) +parse_notification (GVariantBuilder *builder, + GVariant *notification, + GUnixFDList *in_fd_list, + GUnixFDList *out_fd_list, + GError **error) { int i; @@ -310,118 +944,167 @@ check_notification (GVariant *notification, { if (!check_value_type (key, value, G_VARIANT_TYPE_STRING, error)) return FALSE; + + g_variant_builder_add (builder, "{sv}", key, value); } - else if (strcmp (key, "icon") == 0) + else if (strcmp (key, "markup-body") == 0 && impl_version > 1) { - if (!check_serialized_icon (value, error)) + if (!parse_markup_body (builder, value, error)) return FALSE; } + else if (strcmp (key, "icon") == 0) + { + if (!parse_serialized_icon (builder, + value, + in_fd_list, + out_fd_list, + error)) + { + g_prefix_error (error, "invalid icon: "); + return FALSE; + } + } + else if (strcmp (key, "sound") == 0 && impl_version > 1) + { + if (!parse_serialized_sound (builder, + value, + in_fd_list, + out_fd_list, + error)) + { + g_prefix_error (error, "invalid sound: "); + return FALSE; + } + } else if (strcmp (key, "priority") == 0) { - if (!check_priority (value, error)) + if (!parse_priority (builder, value, error)) return FALSE; } else if (strcmp (key, "default-action") == 0) { if (!check_value_type (key, value, G_VARIANT_TYPE_STRING, error)) return FALSE; + + g_variant_builder_add (builder, "{sv}", key, value); } else if (strcmp (key, "default-action-target") == 0) - ; + { + g_variant_builder_add (builder, "{sv}", key, value); + } else if (strcmp (key, "buttons") == 0) { - if (!check_buttons (value, error)) + if (!parse_buttons (builder, value, error)) return FALSE; } - else + else if (strcmp (key, "display-hint") == 0 && impl_version > 1) { - g_set_error (error, - XDG_DESKTOP_PORTAL_ERROR, - XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "%s not valid key", key); - return FALSE; + if (!parse_display_hint (builder, value, error)) + return FALSE; } + else if (strcmp (key, "category") == 0 && impl_version > 1) + { + if (!parse_category (builder, value, error)) + return FALSE; + } + else { + g_debug ("Unsupported property %s filtered from notification", key); + } } return TRUE; } -static GVariant * -maybe_remove_icon (GVariant *notification) +static void +add_finished_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) { - GVariantBuilder n; - int i; + CallData *call_data; + g_autoptr(GError) error = NULL; - g_variant_builder_init (&n, G_VARIANT_TYPE_VARDICT); - for (i = 0; i < g_variant_n_children (notification); i++) - { - const char *key; - g_autoptr(GVariant) value = NULL; + g_assert (g_task_is_valid (result, source_object)); - g_variant_get_child (notification, i, "{&sv}", &key, &value); - if (strcmp (key, "icon") != 0 || xdp_validate_serialized_icon (value, FALSE, NULL, NULL)) - g_variant_builder_add (&n, "{sv}", key, value); - } + call_data = g_task_get_task_data (G_TASK (result)); + g_assert (call_data != NULL); - return g_variant_ref_sink (g_variant_builder_end (&n)); + if (g_task_propagate_boolean (G_TASK (result), &error)) + xdp_dbus_notification_complete_add_notification (XDP_DBUS_NOTIFICATION (source_object), + call_data->inv, + NULL); + else + g_dbus_method_invocation_return_gerror (call_data->inv, error); } static void -handle_add_in_thread_func (GTask *task, - gpointer source_object, - gpointer task_data, +handle_add_in_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; - const char *id; - GVariant *notification; - g_autoptr(GVariant) notification2 = NULL; + CallData *call_data = task_data; + g_auto(GVariantBuilder) builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GError) error = NULL; - REQUEST_AUTOLOCK (request); + CALL_DATA_AUTOLOCK (call_data); - if (!xdp_app_info_is_host (request->app_info) && - !get_notification_allowed (xdp_app_info_get_id (request->app_info))) - return; + if (!xdp_app_info_is_host (call_data->app_info) && + !get_notification_allowed (xdp_app_info_get_id (call_data->app_info))) + { + g_set_error_literal (&error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "Showing notifications is not allowed"); + + g_task_return_error (task, g_steal_pointer (&error)); + return; + } - id = (const char *)g_object_get_data (G_OBJECT (request), "id"); - notification = (GVariant *)g_object_get_data (G_OBJECT (request), "notification"); + if (!parse_notification (&builder, + call_data->notification, + call_data->in_fd_list, + call_data->out_fd_list, + &error)) + { + g_prefix_error (&error, "invalid notification: "); + g_task_return_error (task, g_steal_pointer (&error)); + return; + } - notification2 = maybe_remove_icon (notification); xdp_dbus_impl_notification_call_add_notification (impl, - xdp_app_info_get_id (request->app_info), - id, - notification2, + xdp_app_info_get_id (call_data->app_info), + call_data->id, + g_variant_builder_end (&builder), + call_data->out_fd_list, NULL, add_done, - g_object_ref (request)); + g_object_ref (call_data)); + + g_task_return_boolean (task, TRUE); } static gboolean notification_handle_add_notification (XdpDbusNotification *object, GDBusMethodInvocation *invocation, + GUnixFDList *in_fd_list, const char *arg_id, GVariant *notification) { - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); g_autoptr(GTask) task = NULL; - g_autoptr(GError) error = NULL; - - g_object_set_data_full (G_OBJECT (request), "id", g_strdup (arg_id), g_free); - g_object_set_data_full (G_OBJECT (request), "notification", g_variant_ref (notification), (GDestroyNotify)g_variant_unref); - - if (!check_notification (notification, &error)) - { - g_prefix_error (&error, "invalid notification: "); - g_dbus_method_invocation_return_gerror (invocation, error); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - - task = g_task_new (object, NULL, NULL, NULL); - g_task_set_task_data (task, g_object_ref (request), g_object_unref); + CallData *call_data; + + call_data = call_data_new (invocation, + call->app_info, + call->sender, + arg_id, + notification, + in_fd_list); + task = g_task_new (object, NULL, add_finished_cb, NULL); + g_task_set_task_data (task, call_data, g_object_unref); g_task_run_in_thread (task, handle_add_in_thread_func); - xdp_dbus_notification_complete_add_notification (object, invocation); - return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -430,7 +1113,7 @@ remove_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(CallData) call_data = data; g_autoptr(GError) error = NULL; if (!xdp_dbus_impl_notification_call_remove_notification_finish (impl, result, &error)) @@ -442,8 +1125,8 @@ remove_done (GObject *source, { Pair p; - p.app_id = (char *)xdp_app_info_get_id (request->app_info); - p.id = (char *)g_object_get_data (G_OBJECT (request), "id"); + p.app_id = (char *)xdp_app_info_get_id (call_data->app_info); + p.id = call_data->id; G_LOCK (active); g_hash_table_remove (active, &p); @@ -456,15 +1139,19 @@ notification_handle_remove_notification (XdpDbusNotification *object, GDBusMethodInvocation *invocation, const char *arg_id) { - Request *request = request_from_invocation (invocation); - - g_object_set_data_full (G_OBJECT (request), "id", g_strdup (arg_id), g_free); + XdpCall *call = xdp_call_from_invocation (invocation); + CallData *call_data = call_data_new (invocation, + call->app_info, + call->sender, + arg_id, + NULL, + NULL); xdp_dbus_impl_notification_call_remove_notification (impl, - xdp_app_info_get_id (request->app_info), + xdp_app_info_get_id (call->app_info), arg_id, NULL, - remove_done, g_object_ref (request)); + remove_done, call_data); xdp_dbus_notification_complete_remove_notification (object, invocation); @@ -546,7 +1233,10 @@ notification_iface_init (XdpDbusNotificationIface *iface) static void notification_init (Notification *notification) { - xdp_dbus_notification_set_version (XDP_DBUS_NOTIFICATION (notification), 1); + xdp_dbus_notification_set_version (XDP_DBUS_NOTIFICATION (notification), 2); + g_object_bind_property (G_OBJECT (impl), "supported-options", + G_OBJECT (notification), "supported-options", + G_BINDING_SYNC_CREATE); } static void @@ -559,6 +1249,7 @@ notification_create (GDBusConnection *connection, const char *dbus_name) { g_autoptr(GError) error = NULL; + g_autoptr(GVariant) version = NULL; impl = xdp_dbus_impl_notification_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, @@ -576,6 +1267,9 @@ notification_create (GDBusConnection *connection, notification = g_object_new (notification_get_type (), NULL); active = g_hash_table_new_full (pair_hash, pair_equal, pair_free, g_free); + version = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (impl), "version"); + impl_version = (version != NULL) ? g_variant_get_uint32 (version) : 1; + g_dbus_connection_signal_subscribe (connection, dbus_name, "org.freedesktop.impl.portal.Notification", diff --git a/src/notification.h b/src/notification.h index 7a918f9..ebd40fb 100644 --- a/src/notification.h +++ b/src/notification.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/open-uri.c b/src/open-uri.c index fe09ee2..b5447c2 100644 --- a/src/open-uri.c +++ b/src/open-uri.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -36,12 +38,13 @@ #include #include "open-uri.h" -#include "request.h" +#include "xdp-request.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" -#include "permissions.h" -#include "documents.h" +#include "xdp-permissions.h" +#include "xdp-app-launch-context.h" +#include "xdp-documents.h" #define FILE_MANAGER_DBUS_NAME "org.freedesktop.FileManager1" #define FILE_MANAGER_DBUS_IFACE "org.freedesktop.FileManager1" @@ -131,7 +134,7 @@ get_latest_choice_info (const char *app_id, g_autoptr(GVariant) out_perms = NULL; g_autoptr(GVariant) out_data = NULL; - if (!xdp_dbus_impl_permission_store_call_lookup_sync (get_permission_store (), + if (!xdp_dbus_impl_permission_store_call_lookup_sync (xdp_get_permission_store (), PERMISSION_TABLE, content_type, &out_perms, @@ -239,9 +242,10 @@ launch_application_with_uri (const char *choice_id, { g_autofree char *desktop_id = g_strconcat (choice_id, ".desktop", NULL); g_autoptr(GDesktopAppInfo) info = g_desktop_app_info_new (desktop_id); - g_autoptr(GAppLaunchContext) context = g_app_launch_context_new (); + g_autoptr(XdpAppLaunchContext) xdp_context = xdp_app_launch_context_new (); + GAppLaunchContext *context = G_APP_LAUNCH_CONTEXT (xdp_context); g_autofree char *ruri = NULL; - DocumentFlags flags = DOCUMENT_FLAG_NONE; + XdpDocumentFlags flags = XDP_DOCUMENT_FLAG_NONE; GList uris; if (info == NULL) @@ -259,13 +263,13 @@ launch_application_with_uri (const char *choice_id, g_debug ("Registering %s for %s", uri, choice_id); if (writable) - flags |= DOCUMENT_FLAG_WRITABLE; + flags |= XDP_DOCUMENT_FLAG_WRITABLE; - ruri = register_document (uri, choice_id, flags, &local_error); + ruri = xdp_register_document (uri, choice_id, flags, &local_error); if (ruri == NULL) { g_warning ("Error registering %s for %s: %s", uri, choice_id, local_error->message); - g_propagate_error (error, local_error); + g_propagate_error (error, g_steal_pointer (&local_error)); return FALSE; } } @@ -274,8 +278,7 @@ launch_application_with_uri (const char *choice_id, g_app_launch_context_setenv (context, "PARENT_WINDOW_ID", parent_window); - if (activation_token) - g_app_launch_context_setenv (context, "XDG_ACTIVATION_TOKEN", activation_token); + xdp_app_launch_context_set_activation_token (xdp_context, activation_token); uris.data = (gpointer)ruri; uris.next = NULL; @@ -326,7 +329,7 @@ update_permissions_store (const char *app_id, in_permissions[PERM_APP_THRESHOLD]); - if (!xdp_dbus_impl_permission_store_call_set_permission_sync (get_permission_store (), + if (!xdp_dbus_impl_permission_store_call_set_permission_sync (xdp_get_permission_store (), PERMISSION_TABLE, TRUE, content_type, @@ -347,19 +350,16 @@ send_response_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; + XdpRequest *request = XDP_REQUEST (task_data); guint response; GVariant *options; const char *choice; - GVariantBuilder opt_builder; REQUEST_AUTOLOCK (request); response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "response")); options = (GVariant *)g_object_get_data (G_OBJECT (request), "options"); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - if (response != 0) goto out; @@ -387,10 +387,13 @@ send_response_in_thread_func (GTask *task, out: if (request->exported) { + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&opt_builder)); - request_unexport (request); + xdp_request_unexport (request); } } @@ -399,7 +402,7 @@ app_chooser_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr (Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; @@ -549,7 +552,7 @@ find_recommended_choices (const char *scheme, static void app_info_changed (GAppInfoMonitor *monitor, - Request *request) + XdpRequest *request) { const char *scheme; const char *content_type; @@ -588,7 +591,7 @@ handle_open_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; + XdpRequest *request = XDP_REQUEST (task_data); const char *parent_window; const char *app_id = xdp_app_info_get_id (request->app_info); const char *activation_token; @@ -604,15 +607,18 @@ handle_open_in_thread_func (GTask *task, gint latest_count; gint latest_threshold; gboolean ask_for_content_type; - GVariantBuilder opts_builder; + g_auto(GVariantBuilder) opts_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); gboolean skip_app_chooser = FALSE; - g_auto(XdpFd) fd = -1; + g_autofd int fd = -1; gboolean writable = FALSE; gboolean ask = FALSE; gboolean open_dir = FALSE; gboolean use_default_app = FALSE; const char *reason; + REQUEST_AUTOLOCK (request); + parent_window = (const char *)g_object_get_data (G_OBJECT (request), "parent-window"); uri = g_strdup ((const char *)g_object_get_data (G_OBJECT (request), "uri")); fd = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "fd")); @@ -621,7 +627,7 @@ handle_open_in_thread_func (GTask *task, open_dir = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "open-dir")); activation_token = (const char *)g_object_get_data (G_OBJECT (request), "activation-token"); - REQUEST_AUTOLOCK (request); + g_object_set_data (G_OBJECT (request), "fd", GINT_TO_POINTER (-1)); /* Verify that either uri or fd is set, not both */ if (uri != NULL && fd != -1) @@ -629,32 +635,29 @@ handle_open_in_thread_func (GTask *task, g_warning ("Rejecting invalid open-uri request (both URI and fd are set)"); if (request->exported) { - g_variant_builder_init (&opts_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), XDG_DESKTOP_PORTAL_RESPONSE_OTHER, g_variant_builder_end (&opts_builder)); - request_unexport (request); + xdp_request_unexport (request); } return; } if (uri) { - GError *error = NULL; + g_autoptr (GError) local_error = NULL; - if (!g_uri_is_valid (uri, G_URI_FLAGS_NONE, &error)) + if (!g_uri_is_valid (uri, G_URI_FLAGS_NONE, &local_error)) { - g_debug ("Rejecting open request for invalid uri '%s': %s", uri, error->message); - g_clear_error (&error); + g_debug ("Rejecting open request for invalid uri '%s': %s", uri, local_error->message); /* Reject the request */ if (request->exported) { - g_variant_builder_init (&opts_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), XDG_DESKTOP_PORTAL_RESPONSE_OTHER, g_variant_builder_end (&opts_builder)); - request_unexport (request); + xdp_request_unexport (request); } return; } @@ -666,11 +669,10 @@ handle_open_in_thread_func (GTask *task, if (request->exported) { g_debug ("Rejecting open request as content-type couldn't be fetched for '%s'", uri); - g_variant_builder_init (&opts_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), XDG_DESKTOP_PORTAL_RESPONSE_OTHER, g_variant_builder_end (&opts_builder)); - request_unexport (request); + xdp_request_unexport (request); } return; } @@ -686,7 +688,7 @@ handle_open_in_thread_func (GTask *task, if (path != NULL) { - host_path = get_real_path_for_doc_path (path, request->app_info); + host_path = xdp_get_real_path_for_doc_path (path, request->app_info); if (host_path) { g_debug ("OpenFile: translating path value '%s' to host path '%s'", path, host_path); @@ -712,18 +714,17 @@ handle_open_in_thread_func (GTask *task, if (request->exported) { - g_variant_builder_init (&opts_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), XDG_DESKTOP_PORTAL_RESPONSE_OTHER, g_variant_builder_end (&opts_builder)); - request_unexport (request); + xdp_request_unexport (request); } return; } if (open_dir) { - g_autofree char *real_path = get_real_path_for_doc_path (path, request->app_info); + g_autofree char *real_path = xdp_get_real_path_for_doc_path (path, request->app_info); /* Try opening the directory via the file manager interface, then fall back to a plain URI open */ g_autoptr(GError) local_error = NULL; @@ -763,11 +764,10 @@ handle_open_in_thread_func (GTask *task, } else { - g_variant_builder_init (&opts_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS, g_variant_builder_end (&opts_builder)); - request_unexport (request); + xdp_request_unexport (request); return; } @@ -781,9 +781,6 @@ handle_open_in_thread_func (GTask *task, scheme = g_strdup ("file"); uri = g_filename_to_uri (path, NULL, NULL); g_object_set_data_full (G_OBJECT (request), "uri", g_strdup (uri), g_free); - close (fd); - fd = -1; - g_object_set_data (G_OBJECT (request), "fd", GINT_TO_POINTER (-1)); } g_object_set_data_full (G_OBJECT (request), "scheme", g_strdup (scheme), g_free); @@ -870,19 +867,16 @@ handle_open_in_thread_func (GTask *task, { if (!result) g_debug ("Open request for '%s' failed: %s", uri, error->message); - g_variant_builder_init (&opts_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), result ? XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS : XDG_DESKTOP_PORTAL_RESPONSE_OTHER, g_variant_builder_end (&opts_builder)); - request_unexport (request); + xdp_request_unexport (request); } return; } } - g_variant_builder_init (&opts_builder, G_VARIANT_TYPE_VARDICT); - if (latest_id != NULL) g_variant_builder_add (&opts_builder, "{sv}", "last_choice", g_variant_new_string (latest_id)); else if (default_app != NULL) @@ -905,7 +899,7 @@ handle_open_in_thread_func (GTask *task, request->id, NULL, NULL); - request_set_impl_request (request, impl_request); + xdp_request_set_impl_request (request, impl_request); g_signal_connect_object (monitor, "changed", G_CALLBACK (app_info_changed), request, 0); @@ -922,6 +916,31 @@ handle_open_in_thread_func (GTask *task, g_object_ref (request)); } +static gboolean +handle_scheme_supported (XdpDbusOpenURI *object, + GDBusMethodInvocation *invocation, + const gchar *arg_scheme, + GVariant *arg_options) +{ + g_autoptr(GAppInfo) app_info = NULL; + + if (arg_scheme == NULL || *arg_scheme == '\0') + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Scheme not specified"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + app_info = g_app_info_get_default_for_uri_scheme (arg_scheme); + + g_debug ("Handler for scheme: %s%s found.", arg_scheme, app_info ? "" : " not"); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", app_info != NULL)); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + static gboolean handle_open_uri (XdpDbusOpenURI *object, GDBusMethodInvocation *invocation, @@ -929,7 +948,7 @@ handle_open_uri (XdpDbusOpenURI *object, const gchar *arg_uri, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GTask) task = NULL; gboolean writable; gboolean ask; @@ -962,7 +981,7 @@ handle_open_uri (XdpDbusOpenURI *object, if (activation_token) g_object_set_data_full (G_OBJECT (request), "activation-token", g_strdup (activation_token), g_free); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_open_uri_complete_open_uri (object, invocation, request->id); task = g_task_new (object, NULL, NULL, NULL); @@ -980,7 +999,7 @@ handle_open_file (XdpDbusOpenURI *object, GVariant *arg_fd, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GTask) task = NULL; gboolean writable; gboolean ask; @@ -1005,6 +1024,15 @@ handle_open_file (XdpDbusOpenURI *object, ask = FALSE; g_variant_get (arg_fd, "h", &fd_id); + if (fd_id >= g_unix_fd_list_get_length (fd_list)) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Bad file descriptor index"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + fd = g_unix_fd_list_get (fd_list, fd_id, &error); if (fd == -1) { @@ -1022,7 +1050,7 @@ handle_open_file (XdpDbusOpenURI *object, if (activation_token) g_object_set_data_full (G_OBJECT (request), "activation-token", g_strdup (activation_token), g_free); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_open_uri_complete_open_file (object, invocation, NULL, request->id); task = g_task_new (object, NULL, NULL, NULL); @@ -1040,7 +1068,7 @@ handle_open_directory (XdpDbusOpenURI *object, GVariant *arg_fd, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GTask) task = NULL; int fd_id, fd; const char *activation_token = NULL; @@ -1057,6 +1085,15 @@ handle_open_directory (XdpDbusOpenURI *object, } g_variant_get (arg_fd, "h", &fd_id); + if (fd_id >= g_unix_fd_list_get_length (fd_list)) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Bad file descriptor index"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + fd = g_unix_fd_list_get (fd_list, fd_id, &error); if (fd == -1) { @@ -1075,7 +1112,7 @@ handle_open_directory (XdpDbusOpenURI *object, if (activation_token) g_object_set_data_full (G_OBJECT (request), "activation-token", g_strdup (activation_token), g_free); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_open_uri_complete_open_directory (object, invocation, NULL, request->id); task = g_task_new (object, NULL, NULL, NULL); @@ -1091,12 +1128,13 @@ open_uri_iface_init (XdpDbusOpenURIIface *iface) iface->handle_open_uri = handle_open_uri; iface->handle_open_file = handle_open_file; iface->handle_open_directory = handle_open_directory; + iface->handle_scheme_supported = handle_scheme_supported; } static void open_uri_init (OpenURI *openuri) { - xdp_dbus_open_uri_set_version (XDP_DBUS_OPEN_URI (openuri), 3); + xdp_dbus_open_uri_set_version (XDP_DBUS_OPEN_URI (openuri), 5); } static void diff --git a/src/open-uri.h b/src/open-uri.h index 531252a..f711eba 100644 --- a/src/open-uri.h +++ b/src/open-uri.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/permissions.h b/src/permissions.h deleted file mode 100644 index 528980a..0000000 --- a/src/permissions.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright © 2016 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Authors: - * Matthias Clasen - */ - -#pragma once - -#include -#include "xdp-impl-dbus.h" - -typedef enum _Permission -{ - PERMISSION_UNSET, - PERMISSION_NO, - PERMISSION_YES, - PERMISSION_ASK -} Permission; - -char **get_permissions_sync (const char *app_id, - const char *table, - const char *id); - -void set_permissions_sync (const char *app_id, - const char *table, - const char *id, - const char * const *permissions); - -Permission get_permission_sync (const char *app_id, - const char *table, - const char *id); - -void set_permission_sync (const char *app_id, - const char *table, - const char *id, - Permission permission); - -char **permissions_from_tristate (Permission permission); - -Permission permissions_to_tristate (char **permissions); - -void init_permission_store (GDBusConnection *connection); -XdpDbusImplPermissionStore *get_permission_store (void); diff --git a/src/pipewire.c b/src/pipewire.c index 8543306..39b05c7 100644 --- a/src/pipewire.c +++ b/src/pipewire.c @@ -1,10 +1,12 @@ /* * Copyright © 2017-2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -329,7 +331,7 @@ pipewire_remote_new_sync (struct pw_properties *pipewire_properties, remote->registry = (struct pw_proxy*) pw_core_get_registry (remote->core, PW_VERSION_REGISTRY, 0); - pw_registry_add_listener (remote->registry, + pw_registry_add_listener ((struct pw_registry*)remote->registry, &remote->registry_listener, ®istry_events, remote); diff --git a/src/pipewire.h b/src/pipewire.h index 6061813..f861449 100644 --- a/src/pipewire.h +++ b/src/pipewire.h @@ -1,10 +1,12 @@ /* * Copyright © 2017-2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/power-profile-monitor.c b/src/power-profile-monitor.c index 26917f5..d9e4b12 100644 --- a/src/power-profile-monitor.c +++ b/src/power-profile-monitor.c @@ -1,10 +1,12 @@ /* * Copyright © 2021 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,7 +26,7 @@ #include #include "power-profile-monitor.h" -#include "request.h" +#include "xdp-request.h" #include "xdp-dbus.h" #include "xdp-utils.h" diff --git a/src/power-profile-monitor.h b/src/power-profile-monitor.h index b23ea8b..b5178c9 100644 --- a/src/power-profile-monitor.h +++ b/src/power-profile-monitor.h @@ -1,10 +1,12 @@ /* * Copyright © 2021 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/print.c b/src/print.c index 1e8e194..e20ffad 100644 --- a/src/print.c +++ b/src/print.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -33,7 +35,7 @@ #include #include "print.h" -#include "request.h" +#include "xdp-request.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -67,7 +69,7 @@ print_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; @@ -87,20 +89,54 @@ print_done (GObject *source, if (request->exported) { - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&opt_builder)); - request_unexport (request); + xdp_request_unexport (request); } } +static gboolean +validate_supported_output_file_formats (const char *key, + GVariant *value, + GVariant *options, + GError **error) +{ + const char * const supported_output_file_formats[] = { + "pdf", + "ps", + "svg", + NULL, + }; + g_auto(GStrv) strv = g_variant_dup_strv (value, NULL); + + if (g_strv_length (strv) == 0) + { + g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Empty list of output file formats"); + return FALSE; + } + + for (size_t i = 0; strv && strv[i]; i++) + { + if (!g_strv_contains (supported_output_file_formats, strv[i])) + { + g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Output file format \"%s\" is not one of: pdf, ps, svg", strv[i]); + return FALSE; + } + } + + return TRUE; +} + static XdpOptionKey print_options[] = { { "token", G_VARIANT_TYPE_UINT32, NULL }, { "modal", G_VARIANT_TYPE_BOOLEAN, NULL }, + { "supported_output_file_formats", G_VARIANT_TYPE_STRING_ARRAY, validate_supported_output_file_formats }, }; static gboolean @@ -112,11 +148,12 @@ handle_print (XdpDbusPrint *object, GVariant *arg_fd, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); if (xdp_dbus_impl_lockdown_get_disable_printing (lockdown)) { @@ -142,10 +179,9 @@ handle_print (XdpDbusPrint *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_filter_options (arg_options, &opt_builder, print_options, G_N_ELEMENTS (print_options), NULL); xdp_dbus_impl_print_call_print(impl, @@ -176,7 +212,7 @@ prepare_print_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; @@ -195,9 +231,8 @@ prepare_print_done (GObject *source, if (request->exported) { - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); if (response == 0) xdp_filter_options (options, &opt_builder, @@ -208,13 +243,14 @@ prepare_print_done (GObject *source, response, g_variant_builder_end (&opt_builder)); - request_unexport (request); + xdp_request_unexport (request); } } static XdpOptionKey prepare_print_options[] = { { "modal", G_VARIANT_TYPE_BOOLEAN }, - { "accept_label", G_VARIANT_TYPE_STRING } + { "accept_label", G_VARIANT_TYPE_STRING }, + { "supported_output_file_formats", G_VARIANT_TYPE_STRING_ARRAY, validate_supported_output_file_formats }, }; static gboolean @@ -226,11 +262,12 @@ handle_prepare_print (XdpDbusPrint *object, GVariant *arg_page_setup, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); if (xdp_dbus_impl_lockdown_get_disable_printing (lockdown)) { @@ -255,10 +292,9 @@ handle_prepare_print (XdpDbusPrint *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_filter_options (arg_options, &opt_builder, prepare_print_options, G_N_ELEMENTS (prepare_print_options), NULL); xdp_dbus_impl_print_call_prepare_print (impl, @@ -288,7 +324,7 @@ print_iface_init (XdpDbusPrintIface *iface) static void print_init (Print *print) { - xdp_dbus_print_set_version (XDP_DBUS_PRINT (print), 2); + xdp_dbus_print_set_version (XDP_DBUS_PRINT (print), 3); } static void diff --git a/src/print.h b/src/print.h index 1a4ca50..5f38842 100644 --- a/src/print.h +++ b/src/print.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/proxy-resolver.c b/src/proxy-resolver.c index 5a5ea19..f32fad3 100644 --- a/src/proxy-resolver.c +++ b/src/proxy-resolver.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,7 +26,8 @@ #include #include "proxy-resolver.h" -#include "request.h" +#include "xdp-call.h" +#include "xdp-app-info.h" #include "xdp-dbus.h" #include "xdp-utils.h" @@ -59,9 +62,9 @@ proxy_resolver_handle_lookup (XdpDbusProxyResolver *object, const char *arg_uri) { ProxyResolver *resolver = (ProxyResolver *)object; - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); - if (!xdp_app_info_has_network (request->app_info)) + if (!xdp_app_info_has_network (call->app_info)) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -71,11 +74,11 @@ proxy_resolver_handle_lookup (XdpDbusProxyResolver *object, else { g_auto (GStrv) proxies = NULL; - GError *error = NULL; + g_autoptr (GError) error = NULL; proxies = g_proxy_resolver_lookup (resolver->resolver, arg_uri, NULL, &error); - if (error) - g_dbus_method_invocation_take_error (invocation, error); + if (!proxies) + g_dbus_method_invocation_take_error (invocation, g_steal_pointer (&error)); else g_dbus_method_invocation_return_value (invocation, g_variant_new ("(^as)", proxies)); diff --git a/src/proxy-resolver.h b/src/proxy-resolver.h index ea52393..f24874f 100644 --- a/src/proxy-resolver.h +++ b/src/proxy-resolver.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/realtime.c b/src/realtime.c index 3a02d76..0da44d3 100644 --- a/src/realtime.c +++ b/src/realtime.c @@ -1,10 +1,12 @@ /* * Copyright © 2021 Igalia S.L. * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,8 +26,9 @@ #include #include "realtime.h" -#include "request.h" -#include "permissions.h" +#include "xdp-call.h" +#include "xdp-permissions.h" +#include "xdp-app-info.h" #include "xdp-dbus.h" #include "xdp-utils.h" @@ -58,16 +61,31 @@ G_DEFINE_TYPE_WITH_CODE (Realtime, realtime, XDP_DBUS_TYPE_REALTIME_SKELETON, static gboolean map_pid (XdpAppInfo *app_info, pid_t *pid, pid_t *tid, GError **error) { - if (!xdp_app_info_map_pids (app_info, pid, 1, error)) + ino_t pidns_id; + /* + * the unmapped pid is more useful for debugging, and doesn't leak + * information into the sandbox - see discussion in + * https://github.com/flatpak/xdg-desktop-portal/pull/1655 + */ + const pid_t unmapped_pid = *pid; + + if (!xdp_app_info_get_pidns (app_info, &pidns_id, error)) + { + g_prefix_error (error, "Could not get pidns for pid %d: ", unmapped_pid); + g_warning ("Realtime error: %s", (*error)->message); + return FALSE; + } + + if (pidns_id != 0 && !xdp_map_pids (pidns_id, pid, 1, error)) { - g_prefix_error (error, "Could not map pid: "); + g_prefix_error (error, "Could not map pid %d: ", unmapped_pid); g_warning ("Realtime error: %s", (*error)->message); return FALSE; } - if (!xdp_app_info_map_tids (app_info, *pid, tid, 1, error)) + if (pidns_id != 0 && !xdp_map_tids (pidns_id, *pid, tid, 1, error)) { - g_prefix_error (error, "Could not map tid: "); + g_prefix_error (error, "Could not map tid %d of pid %d: ", *tid, unmapped_pid); g_warning ("Realtime error: %s", (*error)->message); return FALSE; } @@ -102,11 +120,11 @@ handle_make_thread_realtime_with_pid (XdpDbusRealtime *object, guint32 priority) { g_autoptr (GError) error = NULL; - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); pid_t pids[1] = { process }; pid_t tids[1] = { thread }; - const char *app_id = xdp_app_info_get_id (request->app_info); - Permission permission; + const char *app_id = xdp_app_info_get_id (call->app_info); + XdpPermission permission; if (!realtime->rtkit_proxy) { @@ -117,8 +135,8 @@ handle_make_thread_realtime_with_pid (XdpDbusRealtime *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - permission = get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); - if (permission == PERMISSION_NO) + permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); + if (permission == XDP_PERMISSION_NO) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -127,7 +145,7 @@ handle_make_thread_realtime_with_pid (XdpDbusRealtime *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - if (!map_pid (request->app_info, pids, tids, &error)) + if (!map_pid (call->app_info, pids, tids, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; @@ -153,11 +171,11 @@ handle_make_thread_high_priority_with_pid (XdpDbusRealtime *object, gint32 priority) { g_autoptr (GError) error = NULL; - Request *request = request_from_invocation (invocation); + XdpCall *call = xdp_call_from_invocation (invocation); pid_t pids[1] = { process }; pid_t tids[1] = { thread }; - const char *app_id = xdp_app_info_get_id (request->app_info); - Permission permission; + const char *app_id = xdp_app_info_get_id (call->app_info); + XdpPermission permission; if (!realtime->rtkit_proxy) { @@ -168,8 +186,8 @@ handle_make_thread_high_priority_with_pid (XdpDbusRealtime *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - permission = get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); - if (permission == PERMISSION_NO) + permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); + if (permission == XDP_PERMISSION_NO) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, @@ -178,7 +196,7 @@ handle_make_thread_high_priority_with_pid (XdpDbusRealtime *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - if (!map_pid (request->app_info, pids, tids, &error)) + if (!map_pid (call->app_info, pids, tids, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; @@ -235,9 +253,9 @@ load_all_properties (GDBusProxy *proxy) for (guint i = 0; i < G_N_ELEMENTS (properties); ++i) { - GVariant *result; + g_autoptr (GVariant) result = NULL; GVariant *parameters; - GError *error = NULL; + g_autoptr (GError) error = NULL; parameters = g_variant_new ("(ss)", "org.freedesktop.RealtimeKit1", properties[i]); result = g_dbus_proxy_call_sync (proxy, @@ -248,14 +266,13 @@ load_all_properties (GDBusProxy *proxy) NULL, &error); - if (error) + if (!result) { g_warning ("Failed to load RealtimeKit property: %s", error->message); - g_error_free (error); } else { - GVariant *value; + g_autoptr (GVariant) value = NULL; g_variant_get (result, "(v)", &value); if (i == MAX_REALTIME_PRIORITY) @@ -271,8 +288,6 @@ load_all_properties (GDBusProxy *proxy) g_assert_not_reached (); g_dbus_proxy_set_cached_property (proxy, properties[i], value); - g_variant_unref (value); - g_variant_unref (result); } } } diff --git a/src/realtime.h b/src/realtime.h index f35ee71..09e1ea6 100644 --- a/src/realtime.h +++ b/src/realtime.h @@ -1,10 +1,12 @@ /* * Copyright © 2021 Igalia S.L. * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/registry.c b/src/registry.c new file mode 100644 index 0000000..b04af06 --- /dev/null +++ b/src/registry.c @@ -0,0 +1,115 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Jonas Ådahl + */ + +#include "config.h" + +#include "registry.h" + +#include + +#include "xdp-app-info-private.h" +#include "xdp-app-info-test-private.h" +#include "xdp-host-dbus.h" +#include "xdp-utils.h" + +typedef struct _Registry Registry; +typedef struct _RegistryClass RegistryClass; + +struct _Registry +{ + XdpDbusHostRegistrySkeleton parent_instance; +}; + +struct _RegistryClass +{ + XdpDbusHostRegistrySkeletonClass parent_class; +}; + +static Registry *registry; + +GType registry_get_type (void) G_GNUC_CONST; +static void registry_iface_init (XdpDbusHostRegistryIface *iface); + +G_DEFINE_TYPE_WITH_CODE (Registry, registry, + XDP_DBUS_HOST_TYPE_REGISTRY_SKELETON, + G_IMPLEMENT_INTERFACE (XDP_DBUS_HOST_TYPE_REGISTRY, + registry_iface_init)); + +static gboolean +handle_register (XdpDbusHostRegistry *object, + GDBusMethodInvocation *invocation, + const char *arg_app_id, + GVariant *arg_options) +{ + g_autoptr(XdpAppInfo) app_info = NULL; + g_autoptr(GError) error = NULL; + + app_info = xdp_invocation_register_host_app_info_sync (invocation, arg_app_id, + NULL, &error); + if (!app_info) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_FAILED, + "Could not register app ID: %s", + error->message); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + if (g_strcmp0 (xdp_app_info_get_id (app_info), arg_app_id) != 0) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Registered too late"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + xdp_dbus_host_registry_complete_register (object, invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +registry_iface_init (XdpDbusHostRegistryIface *iface) +{ + iface->handle_register = handle_register; +} + +static void +registry_init (Registry *registry) +{ + xdp_dbus_host_registry_set_version (XDP_DBUS_HOST_REGISTRY (registry), 1); +} + +static void +registry_class_init (RegistryClass *klass) +{ +} + +GDBusInterfaceSkeleton * +registry_create (GDBusConnection *connection) +{ + registry = g_object_new (registry_get_type (), NULL); + + return G_DBUS_INTERFACE_SKELETON (registry); +} diff --git a/src/registry.h b/src/registry.h new file mode 100644 index 0000000..ea5d8c4 --- /dev/null +++ b/src/registry.h @@ -0,0 +1,27 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Jonas Ådahl + */ + +#pragma once + +#include + +GDBusInterfaceSkeleton * registry_create (GDBusConnection *connection); diff --git a/src/remote-desktop.c b/src/remote-desktop.c index 8a33a81..5cfbd87 100644 --- a/src/remote-desktop.c +++ b/src/remote-desktop.c @@ -1,10 +1,12 @@ /* * Copyright © 2017-2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -20,13 +22,13 @@ #include "remote-desktop.h" #include "screen-cast.h" -#include "request.h" -#include "restore-token.h" +#include "xdp-request.h" #include "pipewire.h" -#include "call.h" -#include "session.h" +#include "xdp-call.h" +#include "xdp-session.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" +#include "xdp-session-persistence.h" #include "xdp-utils.h" #include @@ -77,7 +79,7 @@ typedef enum _DeviceType typedef struct _RemoteDesktopSession { - Session parent; + XdpSession parent; RemoteDesktopSessionState state; @@ -96,25 +98,16 @@ typedef struct _RemoteDesktopSession gboolean uses_eis; char *restore_token; - PersistMode persist_mode; + XdpSessionPersistenceMode persist_mode; GVariant *restore_data; } RemoteDesktopSession; typedef struct _RemoteDesktopSessionClass { - SessionClass parent_class; + XdpSessionClass parent_class; } RemoteDesktopSessionClass; -GType remote_desktop_session_get_type (void); - -G_DEFINE_TYPE (RemoteDesktopSession, remote_desktop_session, session_get_type ()) - -gboolean -is_remote_desktop_session (Session *session) -{ - return G_TYPE_CHECK_INSTANCE_TYPE (session, - remote_desktop_session_get_type ()); -} +G_DEFINE_TYPE (RemoteDesktopSession, remote_desktop_session, xdp_session_get_type ()) gboolean remote_desktop_session_can_select_sources (RemoteDesktopSession *session) @@ -199,10 +192,10 @@ remote_desktop_session_clipboard_requested (RemoteDesktopSession *session) static RemoteDesktopSession * remote_desktop_session_new (GVariant *options, - Request *request, + XdpRequest *request, GError **error) { - Session *session; + XdpSession *session; GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (request); const char *session_token; @@ -225,7 +218,7 @@ remote_desktop_session_new (GVariant *options, if (session) g_debug ("remote desktop session owned by '%s' created", session->sender); - return (RemoteDesktopSession *)session; + return REMOTE_DESKTOP_SESSION (session); } static void @@ -233,18 +226,18 @@ create_session_done (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; guint response = 2; gboolean should_close_session; - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GError) error = NULL; REQUEST_AUTOLOCK (request); session = g_object_get_qdata (G_OBJECT (request), quark_request_session); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); @@ -261,7 +254,7 @@ create_session_done (GObject *source_object, } if (request->exported && response == 0) { - if (!session_export (session, &error)) + if (!xdp_session_export (session, &error)) { g_warning ("Failed to export session: %s", error->message); response = 2; @@ -270,7 +263,7 @@ create_session_done (GObject *source_object, } should_close_session = FALSE; - session_register (session); + xdp_session_register (session); } else { @@ -286,15 +279,11 @@ create_session_done (GObject *source_object, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results_builder)); - request_unexport (request); - } - else - { - g_variant_builder_clear (&results_builder); + xdp_request_unexport (request); } if (should_close_session) - session_close (session, FALSE); + xdp_session_close (session, FALSE); } static gboolean @@ -302,12 +291,13 @@ handle_create_session (XdpDbusRemoteDesktop *object, GDBusMethodInvocation *invocation, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); @@ -323,18 +313,17 @@ handle_create_session (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - session = (Session *)remote_desktop_session_new (arg_options, request, &error); + session = XDP_SESSION (remote_desktop_session_new (arg_options, request, &error)); if (!session) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -360,8 +349,8 @@ select_devices_done (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; guint response = 2; gboolean should_close_session; g_autoptr(GError) error = NULL; @@ -381,6 +370,7 @@ select_devices_done (GObject *source_object, { g_dbus_error_strip_remote_error (error); g_warning ("A backend call failed: %s", error->message); + g_clear_error (&error); } should_close_session = !request->exported || response != 0; @@ -389,24 +379,25 @@ select_devices_done (GObject *source_object, { if (!results) { - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); } if (should_close_session) { - session_close (session, TRUE); + xdp_session_close (session, TRUE); } else if (!session->closed) { - RemoteDesktopSession *remote_desktop_session = (RemoteDesktopSession *)session; + RemoteDesktopSession *remote_desktop_session = + REMOTE_DESKTOP_SESSION (session); remote_desktop_session->devices_selected = TRUE; } @@ -458,7 +449,7 @@ validate_persist_mode (const char *key, { uint32_t mode = g_variant_get_uint32 (value); - if (mode > PERSIST_MODE_PERSISTENT) + if (mode > XDP_SESSION_PERSISTENCE_MODE_PERSISTENT) { g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, @@ -477,18 +468,18 @@ static XdpOptionKey remote_desktop_select_devices_options[] = { }; static gboolean -replace_remote_desktop_restore_token_with_data (Session *session, +replace_remote_desktop_restore_token_with_data (XdpSession *session, GVariant **in_out_options, GError **error) { - RemoteDesktopSession *remote_desktop_session = (RemoteDesktopSession *) session; + RemoteDesktopSession *remote_desktop_session = REMOTE_DESKTOP_SESSION (session); g_autoptr(GVariant) options = NULL; - PersistMode persist_mode; + XdpSessionPersistenceMode persist_mode; options = *in_out_options; if (!g_variant_lookup (options, "persist_mode", "u", &persist_mode)) - persist_mode = PERSIST_MODE_NONE; + persist_mode = XDP_SESSION_PERSISTENCE_MODE_NONE; remote_desktop_session->persist_mode = persist_mode; xdp_session_persistence_replace_restore_token_with_data (session, @@ -505,17 +496,18 @@ handle_select_devices (XdpDbusRemoteDesktop *object, const char *arg_session_handle, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); - Session *session; + XdpRequest *request = xdp_request_from_invocation (invocation); + XdpSession *session; RemoteDesktopSession *remote_desktop_session; g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options_builder; - GVariant *options; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -527,7 +519,7 @@ handle_select_devices (XdpDbusRemoteDesktop *object, SESSION_AUTOLOCK_UNREF (session); - remote_desktop_session = (RemoteDesktopSession *)session; + remote_desktop_session = REMOTE_DESKTOP_SESSION (session); if (!remote_desktop_session_can_select_devices (remote_desktop_session)) { @@ -550,10 +542,9 @@ handle_select_devices (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_select_devices_options, G_N_ELEMENTS (remote_desktop_select_devices_options), @@ -563,7 +554,7 @@ handle_select_devices (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); /* If 'restore_token' is passed, lookup the corresponding data in the * permission store and / or the GHashTable with transient permissions. @@ -598,7 +589,7 @@ static void replace_restore_remote_desktop_data_with_token (RemoteDesktopSession *remote_desktop_session, GVariant **in_out_results) { - xdp_session_persistence_replace_restore_data_with_token ((Session *) remote_desktop_session, + xdp_session_persistence_replace_restore_data_with_token (XDP_SESSION (remote_desktop_session), REMOTE_DESKTOP_TABLE, in_out_results, &remote_desktop_session->persist_mode, @@ -639,18 +630,18 @@ start_done (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; RemoteDesktopSession *remote_desktop_session; guint response = 2; gboolean should_close_session; - GVariant *results = NULL; + g_autoptr(GVariant) results = NULL; g_autoptr(GError) error = NULL; REQUEST_AUTOLOCK (request); session = g_object_get_qdata (G_OBJECT (request), quark_request_session); - remote_desktop_session = (RemoteDesktopSession *)session; + remote_desktop_session = REMOTE_DESKTOP_SESSION (session); SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); @@ -662,6 +653,7 @@ start_done (GObject *source_object, { g_dbus_error_strip_remote_error (error); g_warning ("A backend call failed: %s", error->message); + g_clear_error (&error); } should_close_session = !request->exported || response != 0; @@ -680,22 +672,23 @@ start_done (GObject *source_object, should_close_session = TRUE; } } - else + + if (!results) { - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - results = g_variant_builder_end (&results_builder); + results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); } if (should_close_session) { - session_close (session, TRUE); + xdp_session_close (session, TRUE); } else if (!session->closed) { @@ -711,17 +704,18 @@ handle_start (XdpDbusRemoteDesktop *object, const char *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); - Session *session; + XdpRequest *request = xdp_request_from_invocation (invocation); + XdpSession *session; RemoteDesktopSession *remote_desktop_session; g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options_builder; - GVariant *options; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -733,7 +727,7 @@ handle_start (XdpDbusRemoteDesktop *object, SESSION_AUTOLOCK_UNREF (session); - remote_desktop_session = (RemoteDesktopSession *)session; + remote_desktop_session = REMOTE_DESKTOP_SESSION (session); switch (remote_desktop_session->state) { case REMOTE_DESKTOP_SESSION_STATE_INIT: @@ -767,11 +761,10 @@ handle_start (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -794,10 +787,10 @@ handle_start (XdpDbusRemoteDesktop *object, } static gboolean -check_notify (Session *session, +check_notify (XdpSession *session, DeviceType device_type) { - RemoteDesktopSession *remote_desktop_session = (RemoteDesktopSession *)session; + RemoteDesktopSession *remote_desktop_session = REMOTE_DESKTOP_SESSION (session); if (!remote_desktop_session->devices_selected || remote_desktop_session->uses_eis) return FALSE; @@ -818,12 +811,12 @@ check_notify (Session *session, } static gboolean -check_position (Session *session, +check_position (XdpSession *session, uint32_t stream, double x, double y) { - RemoteDesktopSession *remote_desktop_session = (RemoteDesktopSession *)session; + RemoteDesktopSession *remote_desktop_session = REMOTE_DESKTOP_SESSION (session); GList *l; for (l = remote_desktop_session->streams; l; l = l->next) @@ -852,13 +845,14 @@ handle_notify_pointer_motion (XdpDbusRemoteDesktop *object, double dx, double dy) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -879,7 +873,6 @@ handle_notify_pointer_motion (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -888,7 +881,7 @@ handle_notify_pointer_motion (XdpDbusRemoteDesktop *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_pointer_motion (impl, session->id, @@ -910,13 +903,14 @@ handle_notify_pointer_motion_absolute (XdpDbusRemoteDesktop *object, double x, double y) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -946,7 +940,6 @@ handle_notify_pointer_motion_absolute (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -956,7 +949,7 @@ handle_notify_pointer_motion_absolute (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_pointer_motion_absolute (impl, session->id, @@ -978,13 +971,14 @@ handle_notify_pointer_button (XdpDbusRemoteDesktop *object, int32_t button, uint32_t state) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1005,7 +999,6 @@ handle_notify_pointer_button (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -1015,7 +1008,7 @@ handle_notify_pointer_button (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_pointer_button (impl, session->id, @@ -1041,13 +1034,14 @@ handle_notify_pointer_axis (XdpDbusRemoteDesktop *object, double dx, double dy) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1068,7 +1062,6 @@ handle_notify_pointer_axis (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_pointer_axis_options, G_N_ELEMENTS (remote_desktop_notify_pointer_axis_options), @@ -1078,7 +1071,7 @@ handle_notify_pointer_axis (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_pointer_axis (impl, session->id, @@ -1099,13 +1092,14 @@ handle_notify_pointer_axis_discrete (XdpDbusRemoteDesktop *object, uint32_t axis, int32_t steps) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1126,7 +1120,6 @@ handle_notify_pointer_axis_discrete (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -1135,7 +1128,7 @@ handle_notify_pointer_axis_discrete (XdpDbusRemoteDesktop *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_pointer_axis_discrete (impl, session->id, @@ -1157,13 +1150,14 @@ handle_notify_keyboard_keycode (XdpDbusRemoteDesktop *object, int32_t keycode, uint32_t state) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1184,7 +1178,6 @@ handle_notify_keyboard_keycode (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -1193,7 +1186,7 @@ handle_notify_keyboard_keycode (XdpDbusRemoteDesktop *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_keyboard_keycode (impl, session->id, @@ -1215,13 +1208,14 @@ handle_notify_keyboard_keysym (XdpDbusRemoteDesktop *object, int32_t keysym, uint32_t state) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1242,7 +1236,6 @@ handle_notify_keyboard_keysym (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -1251,7 +1244,7 @@ handle_notify_keyboard_keysym (XdpDbusRemoteDesktop *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_keyboard_keysym (impl, session->id, @@ -1275,13 +1268,14 @@ handle_notify_touch_down (XdpDbusRemoteDesktop *object, double x, double y) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1311,7 +1305,6 @@ handle_notify_touch_down (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -1320,7 +1313,7 @@ handle_notify_touch_down (XdpDbusRemoteDesktop *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_touch_down (impl, session->id, @@ -1345,13 +1338,14 @@ handle_notify_touch_motion (XdpDbusRemoteDesktop *object, double x, double y) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1381,7 +1375,6 @@ handle_notify_touch_motion (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -1390,7 +1383,7 @@ handle_notify_touch_motion (XdpDbusRemoteDesktop *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_touch_motion (impl, session->id, @@ -1412,13 +1405,14 @@ handle_notify_touch_up (XdpDbusRemoteDesktop *object, GVariant *arg_options, uint32_t slot) { - Call *call = call_from_invocation (invocation); - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1439,7 +1433,6 @@ handle_notify_touch_up (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_notify_options, G_N_ELEMENTS (remote_desktop_notify_options), @@ -1448,7 +1441,7 @@ handle_notify_touch_up (XdpDbusRemoteDesktop *object, g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); xdp_dbus_impl_remote_desktop_call_notify_touch_up (impl, session->id, @@ -1471,15 +1464,16 @@ handle_connect_to_eis (XdpDbusRemoteDesktop *object, const char *arg_session_handle, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; RemoteDesktopSession *remote_desktop_session; g_autoptr(GUnixFDList) out_fd_list = NULL; g_autoptr(GError) error = NULL; - GVariantBuilder options_builder; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); GVariant *fd; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -1491,7 +1485,7 @@ handle_connect_to_eis (XdpDbusRemoteDesktop *object, SESSION_AUTOLOCK_UNREF (session); - if (!is_remote_desktop_session (session)) + if (!IS_REMOTE_DESKTOP_SESSION (session)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -1500,7 +1494,7 @@ handle_connect_to_eis (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - remote_desktop_session = (RemoteDesktopSession *)session; + remote_desktop_session = REMOTE_DESKTOP_SESSION (session); if (remote_desktop_session->uses_eis) { @@ -1529,7 +1523,6 @@ handle_connect_to_eis (XdpDbusRemoteDesktop *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, remote_desktop_connect_to_eis_options, G_N_ELEMENTS (remote_desktop_connect_to_eis_options), @@ -1642,9 +1635,9 @@ remote_desktop_create (GDBusConnection *connection, } static void -remote_desktop_session_close (Session *session) +remote_desktop_session_close (XdpSession *session) { - RemoteDesktopSession *remote_desktop_session = (RemoteDesktopSession *)session; + RemoteDesktopSession *remote_desktop_session = REMOTE_DESKTOP_SESSION (session); remote_desktop_session->state = REMOTE_DESKTOP_SESSION_STATE_CLOSED; @@ -1654,7 +1647,7 @@ remote_desktop_session_close (Session *session) static void remote_desktop_session_finalize (GObject *object) { - RemoteDesktopSession *remote_desktop_session = (RemoteDesktopSession *)object; + RemoteDesktopSession *remote_desktop_session = REMOTE_DESKTOP_SESSION (object); g_list_free_full (remote_desktop_session->streams, (GDestroyNotify)screen_cast_stream_free); @@ -1671,12 +1664,12 @@ static void remote_desktop_session_class_init (RemoteDesktopSessionClass *klass) { GObjectClass *object_class; - SessionClass *session_class; + XdpSessionClass *session_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = remote_desktop_session_finalize; - session_class = (SessionClass *)klass; + session_class = (XdpSessionClass *)klass; session_class->close = remote_desktop_session_close; quark_request_session = diff --git a/src/remote-desktop.h b/src/remote-desktop.h index 6c68c4d..2479270 100644 --- a/src/remote-desktop.h +++ b/src/remote-desktop.h @@ -1,10 +1,12 @@ /* * Copyright © 2017-2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -20,12 +22,24 @@ #include -#include "session.h" +#include "xdp-session.h" #include "screen-cast.h" typedef struct _RemoteDesktopSession RemoteDesktopSession; -gboolean is_remote_desktop_session (Session *session); +GType remote_desktop_session_get_type (void); + +G_GNUC_UNUSED static inline RemoteDesktopSession * +REMOTE_DESKTOP_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_CAST (ptr, remote_desktop_session_get_type (), RemoteDesktopSession); +} + +G_GNUC_UNUSED static inline gboolean +IS_REMOTE_DESKTOP_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_TYPE (ptr, remote_desktop_session_get_type ()); +} GList * remote_desktop_session_get_streams (RemoteDesktopSession *session); diff --git a/src/request.c b/src/request.c deleted file mode 100644 index 9deef53..0000000 --- a/src/request.c +++ /dev/null @@ -1,532 +0,0 @@ -/* - * Copyright © 2016 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Authors: - * Alexander Larsson - * Matthias Clasen - */ - -#include "request.h" -#include "xdp-utils.h" - -#include - -static void request_skeleton_iface_init (XdpDbusRequestIface *iface); - -G_DEFINE_TYPE_WITH_CODE (Request, request, XDP_DBUS_TYPE_REQUEST_SKELETON, - G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_REQUEST, - request_skeleton_iface_init)) - -static void -request_on_signal_response (XdpDbusRequest *object, - guint arg_response, - GVariant *arg_results) -{ - Request *request = (Request *)object; - XdpDbusRequestSkeleton *skeleton = XDP_DBUS_REQUEST_SKELETON (object); - GList *connections, *l; - GVariant *signal_variant; - - connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton)); - - signal_variant = g_variant_ref_sink (g_variant_new ("(u@a{sv})", - arg_response, - arg_results)); - for (l = connections; l != NULL; l = l->next) - { - GDBusConnection *connection = l->data; - g_dbus_connection_emit_signal (connection, - request->sender, - g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), - "org.freedesktop.portal.Request", - "Response", - signal_variant, - NULL); - } - g_variant_unref (signal_variant); - g_list_free_full (connections, g_object_unref); -} - -static gboolean -handle_close (XdpDbusRequest *object, - GDBusMethodInvocation *invocation) -{ - Request *request = (Request *)object; - g_autoptr(GError) error = NULL; - - g_debug ("Handling Close"); - REQUEST_AUTOLOCK (request); - - if (request->exported) - { - if (request->impl_request && - !xdp_dbus_impl_request_call_close_sync (request->impl_request, - NULL, &error)) - { - if (invocation) - g_dbus_method_invocation_return_gerror (invocation, error); - return G_DBUS_METHOD_INVOCATION_HANDLED; - } - - request_unexport (request); - } - - if (invocation) - xdp_dbus_request_complete_close (XDP_DBUS_REQUEST (request), invocation); - - return G_DBUS_METHOD_INVOCATION_HANDLED; -} - -static void -request_skeleton_iface_init (XdpDbusRequestIface *iface) -{ - iface->handle_close = handle_close; - iface->response = request_on_signal_response; -} - -G_LOCK_DEFINE (requests); -static GHashTable *requests; - -static void -request_init (Request *request) -{ - g_mutex_init (&request->mutex); -} - -static void -request_finalize (GObject *object) -{ - Request *request = (Request *)object; - - G_LOCK (requests); - g_hash_table_remove (requests, request->id); - G_UNLOCK (requests); - - g_clear_object (&request->impl_request); - - g_free (request->sender); - g_free (request->id); - g_mutex_clear (&request->mutex); - xdp_app_info_unref (request->app_info); - - G_OBJECT_CLASS (request_parent_class)->finalize (object); -} - -static void -request_class_init (RequestClass *klass) -{ - GObjectClass *gobject_class; - - requests = g_hash_table_new_full (g_str_hash, g_str_equal, - NULL, NULL); - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = request_finalize; -} - -static gboolean -request_authorize_callback (GDBusInterfaceSkeleton *interface, - GDBusMethodInvocation *invocation, - gpointer user_data) -{ - const gchar *request_sender = user_data; - const gchar *sender = g_dbus_method_invocation_get_sender (invocation); - - if (strcmp (sender, request_sender) != 0) - { - g_dbus_method_invocation_return_error (invocation, - G_DBUS_ERROR, - G_DBUS_ERROR_ACCESS_DENIED, - "Portal operation not allowed: Unmatched caller"); - return FALSE; - } - - return TRUE; -} - -/* This is a bit ugly - we need to know where the options vardict is - * in the parameters for each request. Instead of inventing some - * complicated mechanism for each implementation to provide that - * information, just hardcode it here for now. - * - * Note that the pointer returned by this function is good to use - * as long as the invocation object exists, since it points at data - * in the parameters variant. - */ -static const char * -get_token (GDBusMethodInvocation *invocation) -{ - const char *interface; - const char *method; - GVariant *parameters; - g_autoptr(GVariant) options = NULL; - const char *token = NULL; - - interface = g_dbus_method_invocation_get_interface_name (invocation); - method = g_dbus_method_invocation_get_method_name (invocation); - parameters = g_dbus_method_invocation_get_parameters (invocation); - - if (strcmp (interface, "org.freedesktop.portal.Account") == 0) - { - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (interface, "org.freedesktop.portal.Device") == 0) - { - options = g_variant_get_child_value (parameters, 2); - } - else if (strcmp (interface, "org.freedesktop.portal.Email") == 0) - { - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (interface, "org.freedesktop.portal.FileChooser") == 0) - { - options = g_variant_get_child_value (parameters, 2); - } - else if (strcmp (interface, "org.freedesktop.portal.Inhibit") == 0) - { - if (strcmp (method, "Inhibit") == 0) - options = g_variant_get_child_value (parameters, 2); - else if (strcmp (method, "CreateMonitor") == 0) - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (interface, "org.freedesktop.portal.InputCapture") == 0) - { - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (interface, "org.freedesktop.portal.NetworkMonitor") == 0) - { - // no methods - } - else if (strcmp (interface, "org.freedesktop.portal.Notification") == 0) - { - // no request objects - } - else if (strcmp (interface, "org.freedesktop.portal.OpenURI") == 0) - { - options = g_variant_get_child_value (parameters, 2); - } - else if (strcmp (interface, "org.freedesktop.portal.Print") == 0) - { - if (strcmp (method, "Print") == 0) - options = g_variant_get_child_value (parameters, 3); - else if (strcmp (method, "PreparePrint") == 0) - options = g_variant_get_child_value (parameters, 4); - } - else if (strcmp (interface, "org.freedesktop.portal.ProxyResolver") == 0) - { - // no request objects - } - else if (strcmp (interface, "org.freedesktop.portal.Screenshot") == 0) - { - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (interface, "org.freedesktop.portal.ScreenCast") == 0) - { - if (strcmp (method, "CreateSession") == 0 ) - { - options = g_variant_get_child_value (parameters, 0); - } - else if (strcmp (method, "SelectSources") == 0) - { - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (method, "Start") == 0) - { - options = g_variant_get_child_value (parameters, 2); - } - else - { - g_warning ("Support for %s::%s missing in %s", - interface, method, G_STRLOC); - } - } - else if (strcmp (interface, "org.freedesktop.portal.RemoteDesktop") == 0) - { - if (strcmp (method, "CreateSession") == 0 ) - { - options = g_variant_get_child_value (parameters, 0); - } - else if (strcmp (method, "SelectDevices") == 0) - { - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (method, "Start") == 0) - { - options = g_variant_get_child_value (parameters, 2); - } - else - { - g_warning ("Support for %s::%s missing in %s", - interface, method, G_STRLOC); - } - } - else if (strcmp (interface, "org.freedesktop.portal.Clipboard") == 0) - { - // no request objects - } - else if (strcmp (interface, "org.freedesktop.portal.Location") == 0) - { - if (strcmp (method, "CreateSession") == 0 ) - { - options = g_variant_get_child_value (parameters, 0); - } - else if (strcmp (method, "SelectDetails") == 0) - { - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (method, "Start") == 0) - { - options = g_variant_get_child_value (parameters, 2); - } - else - { - g_warning ("Support for %s::%s missing in %s", - interface, method, G_STRLOC); - } - } - else if (strcmp (interface, "org.freedesktop.portal.Settings") == 0) - { - // no request objects - } - else if (strcmp (interface, "org.freedesktop.portal.GameMode") == 0) - { - // no request objects - } - else if (strcmp (interface, "org.freedesktop.portal.Realtime") == 0) - { - // no request objects - } - else if (strcmp (interface, "org.freedesktop.portal.Trash") == 0) - { - // no request objects - } - else if (strcmp (interface, "org.freedesktop.portal.Background") == 0) - { - if (strcmp (method, "RequestBackground") == 0 ) - { - options = g_variant_get_child_value (parameters, 1); - } - } - else if (strcmp (interface, "org.freedesktop.portal.DynamicLauncher") == 0) - { - if (strcmp (method, "PrepareInstall") == 0 ) - { - options = g_variant_get_child_value (parameters, 3); - } - } - else if (strcmp (interface, "org.freedesktop.portal.Wallpaper") == 0) - { - options = g_variant_get_child_value (parameters, 2); - } - else if (strcmp (interface, "org.freedesktop.portal.Camera") == 0) - { - if (strcmp (method, "AccessCamera") == 0 ) - { - options = g_variant_get_child_value (parameters, 0); - } - else if (strcmp (method, "OpenPipeWireRemote") == 0) - { - // no request objects - } - else - { - g_warning ("Support for %s::%s missing in %s", - interface, method, G_STRLOC); - } - } - else if (strcmp (interface, "org.freedesktop.portal.Secret") == 0) - { - options = g_variant_get_child_value (parameters, 1); - } - else if (strcmp (interface, "org.freedesktop.portal.GlobalShortcuts") == 0) - { - if (strcmp (method, "CreateSession") == 0 ) - { - options = g_variant_get_child_value (parameters, 0); - } - else if (strcmp (method, "BindShortcuts") == 0 ) - { - options = g_variant_get_child_value (parameters, 3); - } - else if (strcmp (method, "ListShortcuts") == 0 ) - { - options = g_variant_get_child_value (parameters, 1); - } - else - { - g_warning ("Support for %s::%s missing in %s", - interface, method, G_STRLOC); - } - } - else - { - g_print ("Support for %s missing in " G_STRLOC, interface); - } - - if (options) - g_variant_lookup (options, "handle_token", "&s", &token); - - return token ? token : "t"; -} - -void -request_init_invocation (GDBusMethodInvocation *invocation, XdpAppInfo *app_info) -{ - Request *request; - guint32 r; - char *id = NULL; - const char *token; - g_autofree char *sender = NULL; - int i; - - request = g_object_new (request_get_type (), NULL); - request->sender = g_strdup (g_dbus_method_invocation_get_sender (invocation)); - request->app_info = xdp_app_info_ref (app_info); - - g_object_set_data (G_OBJECT (request), "fd", GINT_TO_POINTER (-1)); - - token = get_token (invocation); - sender = g_strdup (request->sender + 1); - for (i = 0; sender[i]; i++) - if (sender[i] == '.') - sender[i] = '_'; - - id = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token); - - G_LOCK (requests); - - while (g_hash_table_lookup (requests, id) != NULL) - { - r = g_random_int (); - g_free (id); - id = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s/%u", sender, token, r); - } - - request->id = id; - g_hash_table_insert (requests, id, request); - - G_UNLOCK (requests); - - g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (request), - G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); - g_signal_connect (request, "g-authorize-method", - G_CALLBACK (request_authorize_callback), - request->sender); - - - g_object_set_data_full (G_OBJECT (invocation), "request", request, g_object_unref); -} - -Request * -request_from_invocation (GDBusMethodInvocation *invocation) -{ - return g_object_get_data (G_OBJECT (invocation), "request"); -} - -void -request_export (Request *request, - GDBusConnection *connection) -{ - g_autoptr(GError) error = NULL; - - if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (request), - connection, - request->id, - &error)) - { - g_warning ("Error exporting request: %s", error->message); - g_clear_error (&error); - } - - g_object_ref (request); - request->exported = TRUE; -} - -void -request_unexport (Request *request) -{ - int fd; - - fd = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "fd")); - if (fd != -1) - close (fd); - - request->exported = FALSE; - g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (request)); - g_object_unref (request); -} - -void -request_set_impl_request (Request *request, - XdpDbusImplRequest *impl_request) -{ - g_set_object (&request->impl_request, impl_request); -} - -void -close_requests_in_thread_func (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - const char *sender = (const char *)task_data; - GSList *list = NULL; - GSList *l; - GHashTableIter iter; - Request *request; - - G_LOCK (requests); - if (requests) - { - g_hash_table_iter_init (&iter, requests); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&request)) - { - if (strcmp (sender, request->sender) == 0) - list = g_slist_prepend (list, g_object_ref (request)); - } - } - G_UNLOCK (requests); - - for (l = list; l; l = l->next) - { - Request *request = l->data; - - REQUEST_AUTOLOCK (request); - - if (request->exported) - { - if (request->impl_request) - xdp_dbus_impl_request_call_close_sync (request->impl_request, NULL, NULL); - - request_unexport (request); - } - } - - g_slist_free_full (list, g_object_unref); - g_task_return_boolean (task, TRUE); -} - -void -close_requests_for_sender (const char *sender) -{ - GTask *task; - - task = g_task_new (NULL, NULL, NULL, NULL); - g_task_set_task_data (task, g_strdup (sender), g_free); - g_task_run_in_thread (task, close_requests_in_thread_func); - g_object_unref (task); -} - diff --git a/src/rewrite-launchers.c b/src/rewrite-launchers.c index 1d6d7c9..4138852 100644 --- a/src/rewrite-launchers.c +++ b/src/rewrite-launchers.c @@ -1,10 +1,12 @@ /* * Copyright © 2022 Matthew Leeds * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/screen-cast.c b/src/screen-cast.c index 21281cb..562d53a 100644 --- a/src/screen-cast.c +++ b/src/screen-cast.c @@ -1,10 +1,12 @@ /* * Copyright © 2017-2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -22,15 +24,15 @@ #include #include -#include "session.h" +#include "xdp-session.h" #include "screen-cast.h" #include "remote-desktop.h" -#include "request.h" -#include "restore-token.h" -#include "permissions.h" +#include "xdp-request.h" +#include "xdp-permissions.h" #include "pipewire.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" +#include "xdp-session-persistence.h" #include "xdp-utils.h" #define PERMISSION_ITEM(item_id, item_permissions) \ @@ -88,38 +90,43 @@ typedef enum _ScreenCastSessionState typedef struct _ScreenCastSession { - Session parent; + XdpSession parent; ScreenCastSessionState state; GList *streams; char *restore_token; - PersistMode persist_mode; + XdpSessionPersistenceMode persist_mode; GVariant *restore_data; } ScreenCastSession; typedef struct _ScreenCastSessionClass { - SessionClass parent_class; + XdpSessionClass parent_class; } ScreenCastSessionClass; GType screen_cast_session_get_type (void); -G_DEFINE_TYPE (ScreenCastSession, screen_cast_session, session_get_type ()) +G_DEFINE_TYPE (ScreenCastSession, screen_cast_session, xdp_session_get_type ()) +G_GNUC_UNUSED static inline ScreenCastSession * +SCREEN_CAST_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_CAST (ptr, screen_cast_session_get_type (), ScreenCastSession); +} -static gboolean -is_screen_cast_session (Session *session) +G_GNUC_UNUSED static inline gboolean +IS_SCREEN_CAST_SESSION (gpointer ptr) { - return G_TYPE_CHECK_INSTANCE_TYPE (session, screen_cast_session_get_type ()); + return G_TYPE_CHECK_INSTANCE_TYPE (ptr, screen_cast_session_get_type ()); } static ScreenCastSession * screen_cast_session_new (GVariant *options, - Request *request, + XdpRequest *request, GError **error) { - Session *session; + XdpSession *session; GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (request); const char *session_token; @@ -150,11 +157,12 @@ create_session_done (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; guint response = 2; gboolean should_close_session; - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(GError) error = NULL; REQUEST_AUTOLOCK (request); @@ -163,8 +171,6 @@ create_session_done (GObject *source_object, SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - if (!xdp_dbus_impl_screen_cast_call_create_session_finish (impl, &response, NULL, @@ -179,7 +185,7 @@ create_session_done (GObject *source_object, if (request->exported && response == 0) { - if (!session_export (session, &error)) + if (!xdp_session_export (session, &error)) { g_warning ("Failed to export session: %s", error->message); response = 2; @@ -188,7 +194,7 @@ create_session_done (GObject *source_object, } should_close_session = FALSE; - session_register (session); + xdp_session_register (session); } else { @@ -204,15 +210,11 @@ create_session_done (GObject *source_object, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results_builder)); - request_unexport (request); - } - else - { - g_variant_builder_clear (&results_builder); + xdp_request_unexport (request); } if (should_close_session) - session_close (session, FALSE); + xdp_session_close (session, FALSE); } static gboolean @@ -220,12 +222,13 @@ handle_create_session (XdpDbusScreenCast *object, GDBusMethodInvocation *invocation, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - Session *session; - GVariantBuilder options_builder; - GVariant *options; + XdpSession *session; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); @@ -241,18 +244,17 @@ handle_create_session (XdpDbusScreenCast *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - session = (Session *)screen_cast_session_new (arg_options, request, &error); + session = XDP_SESSION (screen_cast_session_new (arg_options, request, &error)); if (!session) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -278,8 +280,8 @@ select_sources_done (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; guint response = 2; gboolean should_close_session; g_autoptr(GError) error = NULL; @@ -307,35 +309,35 @@ select_sources_done (GObject *source_object, { if (!results) { - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); } if (should_close_session) { - session_close (session, TRUE); + xdp_session_close (session, TRUE); } else if (!session->closed) { - if (is_screen_cast_session (session)) + if (IS_SCREEN_CAST_SESSION (session)) { - ScreenCastSession *screen_cast_session = (ScreenCastSession *)session; + ScreenCastSession *screen_cast_session = SCREEN_CAST_SESSION (session); g_assert_cmpint (screen_cast_session->state, ==, SCREEN_CAST_SESSION_STATE_SELECTING_SOURCES); screen_cast_session->state = SCREEN_CAST_SESSION_STATE_SOURCES_SELECTED; } - else if (is_remote_desktop_session (session)) + else if (IS_REMOTE_DESKTOP_SESSION (session)) { RemoteDesktopSession *remote_desktop_session = - (RemoteDesktopSession *)session; + REMOTE_DESKTOP_SESSION (session); remote_desktop_session_sources_selected (remote_desktop_session); } @@ -411,7 +413,7 @@ validate_persist_mode (const char *key, { uint32_t mode = g_variant_get_uint32 (value); - if (mode > PERSIST_MODE_PERSISTENT) + if (mode > XDP_SESSION_PERSISTENCE_MODE_PERSISTENT) { g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, "Invalid persist mode %x", mode); @@ -430,21 +432,21 @@ static XdpOptionKey screen_cast_select_sources_options[] = { }; static gboolean -replace_screen_cast_restore_token_with_data (Session *session, +replace_screen_cast_restore_token_with_data (XdpSession *session, GVariant **in_out_options, GError **error) { g_autoptr(GVariant) options = NULL; - PersistMode persist_mode; + XdpSessionPersistenceMode persist_mode; options = *in_out_options; if (!g_variant_lookup (options, "persist_mode", "u", &persist_mode)) - persist_mode = PERSIST_MODE_NONE; + persist_mode = XDP_SESSION_PERSISTENCE_MODE_NONE; - if (is_remote_desktop_session (session)) + if (IS_REMOTE_DESKTOP_SESSION (session)) { - if (persist_mode != PERSIST_MODE_NONE || + if (persist_mode != XDP_SESSION_PERSISTENCE_MODE_NONE || xdp_variant_contains_key (options, "restore_token")) { g_set_error (error, @@ -455,9 +457,9 @@ replace_screen_cast_restore_token_with_data (Session *session, } } - if (is_screen_cast_session (session)) + if (IS_SCREEN_CAST_SESSION (session)) { - ScreenCastSession *screen_cast_session = (ScreenCastSession *)session; + ScreenCastSession *screen_cast_session = SCREEN_CAST_SESSION (session); screen_cast_session->persist_mode = persist_mode; xdp_session_persistence_replace_restore_token_with_data (session, @@ -479,16 +481,17 @@ handle_select_sources (XdpDbusScreenCast *object, const char *arg_session_handle, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); - Session *session; + XdpRequest *request = xdp_request_from_invocation (invocation); + XdpSession *session; g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options_builder; - GVariant *options; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -500,9 +503,9 @@ handle_select_sources (XdpDbusScreenCast *object, SESSION_AUTOLOCK_UNREF (session); - if (is_screen_cast_session (session)) + if (IS_SCREEN_CAST_SESSION (session)) { - ScreenCastSession *screen_cast_session = (ScreenCastSession *)session; + ScreenCastSession *screen_cast_session = SCREEN_CAST_SESSION (session); switch (screen_cast_session->state) { @@ -530,10 +533,10 @@ handle_select_sources (XdpDbusScreenCast *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } } - else if (is_remote_desktop_session (session)) + else if (IS_REMOTE_DESKTOP_SESSION (session)) { RemoteDesktopSession *remote_desktop_session = - (RemoteDesktopSession *)session; + REMOTE_DESKTOP_SESSION (session); if (!remote_desktop_session_can_select_sources (remote_desktop_session)) { @@ -565,10 +568,9 @@ handle_select_sources (XdpDbusScreenCast *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_filter_options (arg_options, &options_builder, screen_cast_select_sources_options, G_N_ELEMENTS (screen_cast_select_sources_options), @@ -578,7 +580,7 @@ handle_select_sources (XdpDbusScreenCast *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); /* If 'restore_token' is passed, lookup the corresponding data in the * permission store and / or the GHashTable with transient permissions. @@ -594,9 +596,9 @@ handle_select_sources (XdpDbusScreenCast *object, quark_request_session, g_object_ref (session), g_object_unref); - if (is_screen_cast_session (session)) + if (IS_SCREEN_CAST_SESSION (session)) { - ((ScreenCastSession *)session)->state = + SCREEN_CAST_SESSION (session)->state = SCREEN_CAST_SESSION_STATE_SELECTING_SOURCES; } @@ -731,7 +733,7 @@ static void replace_restore_screen_cast_data_with_token (ScreenCastSession *screen_cast_session, GVariant **in_out_results) { - xdp_session_persistence_replace_restore_data_with_token ((Session *) screen_cast_session, + xdp_session_persistence_replace_restore_data_with_token (XDP_SESSION (screen_cast_session), SCREEN_CAST_TABLE, in_out_results, &screen_cast_session->persist_mode, @@ -764,12 +766,12 @@ start_done (GObject *source_object, GAsyncResult *res, gpointer data) { - g_autoptr(Request) request = data; - Session *session; + g_autoptr(XdpRequest) request = data; + XdpSession *session; ScreenCastSession *screen_cast_session; guint response = 2; gboolean should_close_session; - GVariant *results = NULL; + g_autoptr(GVariant) results = NULL; g_autoptr(GError) error = NULL; REQUEST_AUTOLOCK (request); @@ -786,11 +788,12 @@ start_done (GObject *source_object, { g_dbus_error_strip_remote_error (error); g_warning ("A backend call failed: %s", error->message); + g_clear_error (&error); } should_close_session = !request->exported || response != 0; - screen_cast_session = (ScreenCastSession *)session; + screen_cast_session = SCREEN_CAST_SESSION (session); if (request->exported) { @@ -808,19 +811,19 @@ start_done (GObject *source_object, if (!results) { - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - results = g_variant_builder_end (&results_builder); + results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); } if (should_close_session) { - session_close (session, TRUE); + xdp_session_close (session, TRUE); } else if (!session->closed) { @@ -838,17 +841,18 @@ handle_start (XdpDbusScreenCast *object, const char *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); - Session *session; + XdpRequest *request = xdp_request_from_invocation (invocation); + XdpSession *session; ScreenCastSession *screen_cast_session; g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options_builder; - GVariant *options; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); - session = acquire_session (arg_session_handle, request); + session = xdp_session_from_request (arg_session_handle, request); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -860,7 +864,7 @@ handle_start (XdpDbusScreenCast *object, SESSION_AUTOLOCK_UNREF (session); - screen_cast_session = (ScreenCastSession *)session; + screen_cast_session = SCREEN_CAST_SESSION (session); switch (screen_cast_session->state) { case SCREEN_CAST_SESSION_STATE_SOURCES_SELECTED: @@ -902,11 +906,10 @@ handle_start (XdpDbusScreenCast *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); - options = g_variant_builder_end (&options_builder); + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); g_object_set_qdata_full (G_OBJECT (request), quark_request_session, @@ -936,8 +939,8 @@ handle_open_pipewire_remote (XdpDbusScreenCast *object, const char *arg_session_handle, GVariant *arg_options) { - Call *call = call_from_invocation (invocation); - Session *session; + XdpCall *call = xdp_call_from_invocation (invocation); + XdpSession *session; GList *streams; PipeWireRemote *remote; g_autoptr(GUnixFDList) out_fd_list = NULL; @@ -945,7 +948,7 @@ handle_open_pipewire_remote (XdpDbusScreenCast *object, int fd_id; g_autoptr(GError) error = NULL; - session = acquire_session_from_call (arg_session_handle, call); + session = xdp_session_from_call (arg_session_handle, call); if (!session) { g_dbus_method_invocation_return_error (invocation, @@ -957,16 +960,16 @@ handle_open_pipewire_remote (XdpDbusScreenCast *object, SESSION_AUTOLOCK_UNREF (session); - if (is_screen_cast_session (session)) + if (IS_SCREEN_CAST_SESSION (session)) { - ScreenCastSession *screen_cast_session = (ScreenCastSession *)session; + ScreenCastSession *screen_cast_session = SCREEN_CAST_SESSION (session); streams = screen_cast_session->streams; } - else if (is_remote_desktop_session (session)) + else if (IS_REMOTE_DESKTOP_SESSION (session)) { RemoteDesktopSession *remote_desktop_session = - (RemoteDesktopSession *)session; + REMOTE_DESKTOP_SESSION (session); streams = remote_desktop_session_get_streams (remote_desktop_session); } @@ -1117,9 +1120,9 @@ screen_cast_create (GDBusConnection *connection, } static void -screen_cast_session_close (Session *session) +screen_cast_session_close (XdpSession *session) { - ScreenCastSession *screen_cast_session = (ScreenCastSession *)session; + ScreenCastSession *screen_cast_session = SCREEN_CAST_SESSION (session); screen_cast_session->state = SCREEN_CAST_SESSION_STATE_CLOSED; @@ -1135,7 +1138,7 @@ screen_cast_session_close (Session *session) static void screen_cast_session_finalize (GObject *object) { - ScreenCastSession *screen_cast_session = (ScreenCastSession *)object; + ScreenCastSession *screen_cast_session = SCREEN_CAST_SESSION (object); g_clear_pointer (&screen_cast_session->restore_token, g_free); g_clear_pointer (&screen_cast_session->restore_data, g_variant_unref); @@ -1155,11 +1158,11 @@ static void screen_cast_session_class_init (ScreenCastSessionClass *klass) { GObjectClass *object_class; - SessionClass *session_class; + XdpSessionClass *session_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = screen_cast_session_finalize; - session_class = (SessionClass *)klass; + session_class = (XdpSessionClass *)klass; session_class->close = screen_cast_session_close; } diff --git a/src/screen-cast.h b/src/screen-cast.h index 60bc24b..097fa20 100644 --- a/src/screen-cast.h +++ b/src/screen-cast.h @@ -1,10 +1,12 @@ /* * Copyright © 2017-2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/screenshot.c b/src/screenshot.c index 3919788..40d092e 100644 --- a/src/screenshot.c +++ b/src/screenshot.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -33,9 +35,9 @@ #include #include "screenshot.h" -#include "permissions.h" -#include "request.h" -#include "documents.h" +#include "xdp-permissions.h" +#include "xdp-request.h" +#include "xdp-documents.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -69,7 +71,7 @@ G_DEFINE_TYPE_WITH_CODE (Screenshot, screenshot, XDP_DBUS_TYPE_SCREENSHOT_SKELET screenshot_iface_init)); static void -send_response (Request *request, +send_response (XdpRequest *request, guint response, GVariant *results) { @@ -77,7 +79,12 @@ send_response (Request *request, { g_debug ("sending response: %d", response); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); - request_unexport (request); + xdp_request_unexport (request); + } + else + { + g_variant_ref_sink (results); + g_variant_unref (results); } } @@ -87,8 +94,9 @@ send_response_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = task_data; - GVariantBuilder results; + XdpRequest *request = task_data; + g_auto(GVariantBuilder) results = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); guint response; GVariant *options; g_autoptr(GError) error = NULL; @@ -96,8 +104,6 @@ send_response_in_thread_func (GTask *task, REQUEST_AUTOLOCK (request); - g_variant_builder_init (&results, G_VARIANT_TYPE_VARDICT); - response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "response")); options = (GVariant *)g_object_get_data (G_OBJECT (request), "options"); @@ -119,7 +125,7 @@ send_response_in_thread_func (GTask *task, if (xdp_app_info_is_host (request->app_info)) ruri = g_strdup (uri); else - ruri = register_document (uri, xdp_app_info_get_id (request->app_info), DOCUMENT_FLAG_DELETABLE, &error); + ruri = xdp_register_document (uri, xdp_app_info_get_id (request->app_info), XDP_DOCUMENT_FLAG_DELETABLE, &error); if (ruri == NULL) g_warning ("Failed to register %s: %s", uri, error->message); @@ -152,7 +158,7 @@ screenshot_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; @@ -189,21 +195,21 @@ handle_screenshot_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; + XdpRequest *request = XDP_REQUEST (task_data); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder opt_builder; - Permission permission; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + XdpPermission permission; GVariant *options; gboolean permission_store_checked = FALSE; gboolean interactive; + gboolean modal; const char *parent_window; const char *app_id; REQUEST_AUTOLOCK (request); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - app_id = xdp_app_info_get_id (request->app_info); parent_window = ((const char *)g_object_get_data (G_OBJECT (request), "parent-window")); options = ((GVariant *)g_object_get_data (G_OBJECT (request), "options")); @@ -211,33 +217,38 @@ handle_screenshot_in_thread_func (GTask *task, if (xdp_dbus_impl_screenshot_get_version (impl) < 2) goto query_impl; - permission = get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); + permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); if (!g_variant_lookup (options, "interactive", "b", &interactive)) interactive = FALSE; - if (!interactive && permission != PERMISSION_YES) + if (!g_variant_lookup (options, "modal", "b", &modal)) + modal = TRUE; + + if (!interactive && permission != XDP_PERMISSION_YES) { g_autoptr(GVariant) access_results = NULL; - GVariantBuilder access_opt_builder; + g_auto(GVariantBuilder) access_opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autofree gchar *subtitle = NULL; g_autofree gchar *title = NULL; const gchar *body; guint access_response = 2; - if (permission == PERMISSION_NO) + if (permission == XDP_PERMISSION_NO) { send_response (request, 2, g_variant_builder_end (&opt_builder)); return; } - g_variant_builder_init (&access_opt_builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&access_opt_builder, "{sv}", "deny_label", g_variant_new_string (_("Deny"))); g_variant_builder_add (&access_opt_builder, "{sv}", "grant_label", g_variant_new_string (_("Allow"))); g_variant_builder_add (&access_opt_builder, "{sv}", "icon", g_variant_new_string ("applets-screenshooter-symbolic")); + g_variant_builder_add (&access_opt_builder, "{sv}", + "modal", g_variant_new_boolean (modal)); if (g_strcmp0 (app_id, "") != 0) { @@ -286,8 +297,8 @@ handle_screenshot_in_thread_func (GTask *task, return; } - if (permission == PERMISSION_UNSET) - set_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID, access_response == 0 ? PERMISSION_YES : PERMISSION_NO); + if (permission == XDP_PERMISSION_UNSET) + xdp_set_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_ID, access_response == 0 ? XDP_PERMISSION_YES : XDP_PERMISSION_NO); if (access_response != 0) { @@ -313,7 +324,7 @@ handle_screenshot_in_thread_func (GTask *task, return; } - request_set_impl_request (request, impl_request); + xdp_request_set_impl_request (request, impl_request); xdp_filter_options (options, &opt_builder, screenshot_options, G_N_ELEMENTS (screenshot_options), @@ -342,7 +353,7 @@ handle_screenshot (XdpDbusScreenshot *object, const gchar *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GTask) task = NULL; g_debug ("Handle Screenshot"); @@ -353,7 +364,7 @@ handle_screenshot (XdpDbusScreenshot *object, g_variant_ref (arg_options), (GDestroyNotify)g_variant_unref); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_screenshot_complete_screenshot (object, invocation, request->id); task = g_task_new (object, NULL, NULL, NULL); @@ -368,7 +379,7 @@ pick_color_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) options = NULL; g_autoptr(GError) error = NULL; @@ -403,10 +414,11 @@ handle_pick_color (XdpDbusScreenshot *object, const gchar *arg_parent_window, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); REQUEST_AUTOLOCK (request); @@ -422,10 +434,9 @@ handle_pick_color (XdpDbusScreenshot *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_filter_options (arg_options, &opt_builder, pick_color_options, G_N_ELEMENTS (pick_color_options), NULL); diff --git a/src/screenshot.h b/src/screenshot.h index 07577d1..0eed934 100644 --- a/src/screenshot.h +++ b/src/screenshot.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/sd-escape.c b/src/sd-escape.c index 25b4492..869a767 100644 --- a/src/sd-escape.c +++ b/src/sd-escape.c @@ -235,7 +235,7 @@ int cunescape_one(const char *p, gsize length, guint32 *ret, gboolean *eight_bit } /** - * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8 + * utf8_encode_unichar: Encode single UCS-4 character as UTF-8 * @out_utf8: output buffer of at least 4 bytes or NULL * @g: UCS-4 character to encode * @@ -296,7 +296,7 @@ gssize cunescape_length_with_prefix(const char *s, gsize length, const char *pre else pl = strlen(prefix); - ans = g_new(char, pl+length+1); + ans = g_new (char, pl+length+1); if (!ans) return -ENOMEM; diff --git a/src/secret.c b/src/secret.c index ac754a1..6f7f58a 100644 --- a/src/secret.c +++ b/src/secret.c @@ -1,10 +1,12 @@ /* * Copyright © 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -33,7 +35,7 @@ #include #include "secret.h" -#include "request.h" +#include "xdp-request.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -71,11 +73,10 @@ send_response_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = task_data; + XdpRequest *request = task_data; guint response; - GVariantBuilder new_results; - - g_variant_builder_init (&new_results, G_VARIANT_TYPE_VARDICT); + g_auto(GVariantBuilder) new_results = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); REQUEST_AUTOLOCK (request); @@ -86,7 +87,7 @@ send_response_in_thread_func (GTask *task, xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&new_results)); - request_unexport (request); + xdp_request_unexport (request); } } @@ -95,7 +96,7 @@ retrieve_secret_done (GObject *source, GAsyncResult *result, gpointer data) { - g_autoptr(Request) request = data; + g_autoptr(XdpRequest) request = data; guint response = 2; g_autoptr(GVariant) results = NULL; g_autoptr(GError) error = NULL; @@ -126,11 +127,12 @@ handle_retrieve_secret (XdpDbusSecret *object, GVariant *arg_fd, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); const char *app_id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; - GVariantBuilder options; + g_auto(GVariantBuilder) options = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); REQUEST_AUTOLOCK (request); @@ -146,8 +148,6 @@ handle_retrieve_secret (XdpDbusSecret *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); - if (!xdp_filter_options (arg_options, &options, retrieve_secret_options, G_N_ELEMENTS (retrieve_secret_options), &error)) @@ -156,8 +156,8 @@ handle_retrieve_secret (XdpDbusSecret *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } - request_set_impl_request (request, impl_request); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_secret_complete_retrieve_secret (object, invocation, NULL, request->id); diff --git a/src/secret.h b/src/secret.h index 9c3e0b1..fd8e26c 100644 --- a/src/secret.h +++ b/src/secret.h @@ -1,10 +1,12 @@ /* * Copyright © 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/settings.c b/src/settings.c index 4890c03..98f93f4 100644 --- a/src/settings.c +++ b/src/settings.c @@ -1,10 +1,12 @@ /* * Copyright © 2018 Igalia S.L. * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -28,8 +30,8 @@ #include "settings.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" +#include "xdp-portal-impl.h" #include "xdp-utils.h" -#include "portal-impl.h" typedef struct _Settings Settings; typedef struct _SettingsClass SettingsClass; @@ -54,41 +56,99 @@ G_DEFINE_TYPE_WITH_CODE (Settings, settings, XDP_DBUS_TYPE_SETTINGS_SKELETON, G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_SETTINGS, settings_iface_init)); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (Settings, g_object_unref) + +static void +merge_impl_settings (GHashTable *merged, + GVariant *settings) +{ + GVariantIter iter; + const char *namespace; + GVariant *nsvalue; + + g_variant_iter_init (&iter, settings); + while (g_variant_iter_next (&iter, "{&s@a{sv}}", &namespace, &nsvalue)) + { + g_autoptr (GVariant) owned_nsvalue = NULL; + g_autofree char *owned_namespace = NULL; + g_autoptr (GVariantDict) dict = NULL; + GVariantIter iter2; + const char *key; + GVariant *value; + + owned_nsvalue = nsvalue; + + if (!g_hash_table_steal_extended (merged, namespace, + (gpointer *)&owned_namespace, + (gpointer *)&dict)) + { + dict = g_variant_dict_new (NULL); + owned_namespace = g_strdup (namespace); + } + + g_variant_iter_init (&iter2, nsvalue); + while (g_variant_iter_loop (&iter2, "{sv}", &key, &value)) + g_variant_dict_insert_value (dict, key, value); + + g_hash_table_insert (merged, + g_steal_pointer (&owned_namespace), + g_steal_pointer (&dict)); + } +} + +static GVariant * +merged_to_variant (GHashTable *merged) +{ + g_auto(GVariantBuilder) builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{sa{sv}})")); + const char *namespace; + GVariantDict *dict; + GHashTableIter iter; + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sa{sv}}")); + + g_hash_table_iter_init (&iter, merged); + while (g_hash_table_iter_next (&iter, + (gpointer *)&namespace, + (gpointer *)&dict)) + { + g_variant_builder_add (&builder, "{s@a{sv}}", + namespace, + g_variant_dict_end (dict)); + } + + g_variant_builder_close (&builder); + + return g_variant_ref_sink (g_variant_builder_end (&builder)); +} + static gboolean settings_handle_read_all (XdpDbusSettings *object, GDBusMethodInvocation *invocation, const char * const *arg_namespaces) { - g_autoptr(GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("(a{sa{sv}})")); + g_autoptr(GHashTable) merged = NULL; + g_autoptr(GVariant) settings = NULL; int j; - g_variant_builder_open (builder, G_VARIANT_TYPE ("a{sa{sv}}")); + merged = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + (GDestroyNotify) g_variant_dict_unref); - for (j = 0; j < n_impls; j++) + for (j = n_impls - 1; j >= 0; j--) { g_autoptr(GError) error = NULL; g_autoptr(GVariant) impl_value = NULL; if (!xdp_dbus_impl_settings_call_read_all_sync (impls[j], arg_namespaces, &impl_value, NULL, &error)) - { - g_warning ("Failed to ReadAll() from Settings implementation: %s", error->message); - } + g_warning ("Failed to ReadAll() from Settings implementation: %s", error->message); else - { - size_t i; - - for (i = 0; i < g_variant_n_children (impl_value); ++i) - { - g_autoptr(GVariant) child = g_variant_get_child_value (impl_value, i); - g_variant_builder_add_value (builder, child); - } - } + merge_impl_settings (merged, impl_value); } - g_variant_builder_close (builder); - - g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (builder)); + settings = merged_to_variant (merged); + g_dbus_method_invocation_return_value (invocation, settings); return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -103,7 +163,7 @@ settings_handle_read (XdpDbusSettings *object, g_debug ("Read %s %s", arg_namespace, arg_key); - for (i = 0; i < n_impls; i++) + for (i = 0; i < n_impls; i++) { g_autoptr(GError) error = NULL; g_autoptr(GVariant) impl_value = NULL; @@ -215,7 +275,7 @@ GDBusInterfaceSkeleton * settings_create (GDBusConnection *connection, GPtrArray *implementations) { - Settings *settings; + g_autoptr(Settings) settings = NULL; g_autoptr(GError) error = NULL; int i; int n_impls_tmp; @@ -227,7 +287,7 @@ settings_create (GDBusConnection *connection, for (i = 0; i < n_impls_tmp; i++) { - PortalImplementation *impl = g_ptr_array_index (implementations, i); + XdpPortalImplementation *impl = g_ptr_array_index (implementations, i); const char *dbus_name = impl->dbus_name; XdpDbusImplSettings *impl_proxy = @@ -253,5 +313,5 @@ settings_create (GDBusConnection *connection, return NULL; } - return G_DBUS_INTERFACE_SKELETON (settings); + return G_DBUS_INTERFACE_SKELETON (g_steal_pointer (&settings)); } diff --git a/src/settings.h b/src/settings.h index a6419e0..898cd73 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,10 +1,12 @@ /* * Copyright © 2018 Igalia S.L. * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/trash.c b/src/trash.c index 71920d0..e8c9eca 100644 --- a/src/trash.c +++ b/src/trash.c @@ -1,10 +1,12 @@ /* * Copyright © 2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -33,8 +35,9 @@ #include #include "trash.h" -#include "request.h" -#include "documents.h" +#include "xdp-call.h" +#include "xdp-documents.h" +#include "xdp-app-info.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -101,18 +104,26 @@ handle_trash_file (XdpDbusTrash *object, GUnixFDList *fd_list, GVariant *arg_fd) { - Request *request = request_from_invocation (invocation); - int idx, fd; + XdpCall *call = xdp_call_from_invocation (invocation); + int idx; + g_autofd int fd = -1; guint result; g_debug ("Handling TrashFile"); - REQUEST_AUTOLOCK (request); - g_variant_get (arg_fd, "h", &idx); + if (idx >= g_unix_fd_list_get_length (fd_list)) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Bad file descriptor index"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + fd = g_unix_fd_list_get (fd_list, idx, NULL); - result = trash_file (request->app_info, request->sender, fd); + result = trash_file (call->app_info, call->sender, fd); xdp_dbus_trash_complete_trash_file (object, invocation, NULL, result); diff --git a/src/trash.h b/src/trash.h index 484b2e2..f788224 100644 --- a/src/trash.h +++ b/src/trash.h @@ -1,10 +1,12 @@ /* * Copyright © 2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/usb.c b/src/usb.c new file mode 100644 index 0000000..efc2579 --- /dev/null +++ b/src/usb.c @@ -0,0 +1,1567 @@ +/* + * Copyright © 2023-2024 GNOME Foundation Inc. + * 2020 Endless OS Foundation LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Georges Basile Stavracas Neto + * Hubert Figuière + * Ryan Gonzalez + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "usb.h" +#include "xdp-request.h" +#include "xdp-permissions.h" +#include "xdp-session.h" +#include "xdp-dbus.h" +#include "xdp-impl-dbus.h" +#include "xdp-utils.h" +#include "xdp-usb-query.h" + +#define PERMISSION_TABLE "usb" +#define PERMISSION_ID "usb" +#define MAX_DEVICES 8 + +/* TODO: + * + * AccessDevices() + * - Check if backend is returning appropriate device ids + * - Check if backend is not increasing permissions + * - Save allowed devices in the permission store + */ + +struct _XdpUsb +{ + XdpDbusUsbSkeleton parent_instance; + + GHashTable *ids_to_devices; + GHashTable *syspaths_to_ids; + + GHashTable *sessions; + GHashTable *sender_infos; + + GUdevClient *gudev_client; +}; + +#define XDP_TYPE_USB (xdp_usb_get_type ()) +G_DECLARE_FINAL_TYPE (XdpUsb, xdp_usb, XDP, USB, XdpDbusUsbSkeleton) + +static void xdp_usb_iface_init (XdpDbusUsbIface *iface); + +G_DEFINE_FINAL_TYPE_WITH_CODE (XdpUsb, xdp_usb, XDP_DBUS_TYPE_USB_SKELETON, + G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_USB, xdp_usb_iface_init)); + +struct _XdpUsbSession +{ + XdpSession parent; + + GHashTable *available_devices; +}; + +#define XDP_TYPE_USB_SESSION (xdp_usb_session_get_type ()) +G_DECLARE_FINAL_TYPE (XdpUsbSession, + xdp_usb_session, + XDP, USB_SESSION, + XdpSession) + +G_DEFINE_TYPE (XdpUsbSession, xdp_usb_session, xdp_session_get_type ()) + +typedef struct +{ + char *device_id; + gboolean writable; +} UsbDeviceAcquireData; + +typedef struct _UsbOwnedDevice +{ + gatomicrefcount ref_count; + + char *sender_name; + char *device_id; + int fd; +} UsbOwnedDevice; + +typedef struct _UsbSenderInfo +{ + gatomicrefcount ref_count; + + char *sender_name; + XdpAppInfo *app_info; + + GHashTable *pending_devices; /* object_path → GPtrArray */ + + GHashTable *owned_devices; /* device id → UsbOwnedDevices */ +} UsbSenderInfo; + +static XdpDbusImplUsb *usb_impl; +static XdpUsb *usb; + +static void usb_device_acquire_data_free (UsbDeviceAcquireData *acquire_data); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (UsbDeviceAcquireData, usb_device_acquire_data_free) + +static void usb_owned_device_unref (UsbOwnedDevice *owned_device); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (UsbOwnedDevice, usb_owned_device_unref) + +static void usb_sender_info_unref (UsbSenderInfo *sender_info); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (UsbSenderInfo, usb_sender_info_unref) + +static gboolean +is_gudev_device_suitable (GUdevDevice *device) +{ + const char *device_file = NULL; + const char *devtype = NULL; + + g_assert (G_UDEV_IS_DEVICE (device)); + g_return_val_if_fail (g_strcmp0 (g_udev_device_get_subsystem (device), "usb") == 0, + FALSE); + + device_file = g_udev_device_get_device_file (device); + if (!device_file) + return FALSE; + + devtype = g_udev_device_get_property (device, "DEVTYPE"); + if (!devtype || g_strcmp0 (devtype, "usb_device") != 0) + return FALSE; + + return TRUE; +} + +static char * +unique_permission_id_for_device (GUdevDevice *device) +{ + g_autoptr(GString) permission_id = NULL; + const char *property; + + permission_id = g_string_new ("usb:"); + + property = g_udev_device_get_property (device, "ID_VENDOR_ID"); + if (property) + g_string_append (permission_id, property); + + property = g_udev_device_get_property (device, "ID_MODEL_ID"); + g_string_append_printf (permission_id, "%s%s", "/", property ? property : ""); + + property = g_udev_device_get_property (device, "ID_SERIAL"); + g_string_append_printf (permission_id, "%s%s", "/", property ? property : ""); + + return g_string_free (g_steal_pointer (&permission_id), FALSE); +} + +static void +usb_device_acquire_data_free (UsbDeviceAcquireData *acquire_data) +{ + g_return_if_fail (acquire_data != NULL); + + g_clear_pointer (&acquire_data->device_id, g_free); + g_clear_pointer (&acquire_data, g_free); +} + +static UsbOwnedDevice * +usb_owned_device_ref (UsbOwnedDevice *owned_device) +{ + g_return_val_if_fail (owned_device != NULL, NULL); + + g_atomic_ref_count_inc (&owned_device->ref_count); + + return owned_device; +} + +static void +usb_owned_device_unref (UsbOwnedDevice *owned_device) +{ + g_return_if_fail (owned_device != NULL); + + if (g_atomic_ref_count_dec (&owned_device->ref_count)) + { + g_clear_fd (&owned_device->fd, NULL); + g_clear_pointer (&owned_device->device_id, g_free); + g_clear_pointer (&owned_device, g_free); + } +} + +static void +usb_sender_info_unref (UsbSenderInfo *sender_info) +{ + g_return_if_fail (sender_info != NULL); + + if (g_atomic_ref_count_dec (&sender_info->ref_count)) + { + g_clear_object (&sender_info->app_info); + g_clear_pointer (&sender_info->sender_name, g_free); + g_clear_pointer (&sender_info->owned_devices, g_hash_table_destroy); + g_clear_pointer (&sender_info->pending_devices, g_hash_table_destroy); + g_clear_pointer (&sender_info, g_free); + } +} + +static UsbSenderInfo * +usb_sender_info_new (const char *sender_name, + XdpAppInfo *app_info) +{ + g_autoptr(UsbSenderInfo) sender_info = NULL; + + sender_info = g_new0 (UsbSenderInfo, 1); + g_atomic_ref_count_init (&sender_info->ref_count); + sender_info->sender_name = g_strdup (sender_name); + sender_info->app_info = g_object_ref (app_info); + sender_info->owned_devices = + g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) usb_owned_device_unref); + sender_info->pending_devices = + g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_ptr_array_unref); + + return g_steal_pointer (&sender_info); +} + +static UsbSenderInfo * +usb_sender_info_from_sender (XdpUsb *self, + const char *sender, + XdpAppInfo *app_info) +{ + g_autoptr(UsbSenderInfo) sender_info = NULL; + + + sender_info = g_hash_table_lookup (self->sender_infos, sender); + + if (!sender_info) + { + sender_info = usb_sender_info_new (sender, app_info); + g_hash_table_insert (self->sender_infos, g_strdup (sender), sender_info); + } + + g_assert (sender_info != NULL); + g_atomic_ref_count_inc (&sender_info->ref_count); + + return g_steal_pointer (&sender_info); +} + +static UsbSenderInfo * +usb_sender_info_from_call (XdpUsb *self, + XdpCall *call) +{ + g_return_val_if_fail (call != NULL, NULL); + + return usb_sender_info_from_sender (self, call->sender, call->app_info); +} + +static UsbSenderInfo * +usb_sender_info_from_request (XdpUsb *self, + XdpRequest *request) +{ + g_return_val_if_fail (request != NULL, NULL); + + return usb_sender_info_from_sender (self, request->sender, request->app_info); +} + +static void +usb_sender_info_acquire_device (UsbSenderInfo *sender_info, + const char *device_id, + int fd) +{ + g_autoptr(UsbOwnedDevice) owned_device = NULL; + + g_assert (sender_info != NULL); + g_assert (!g_hash_table_contains (sender_info->owned_devices, device_id)); + + owned_device = g_new0 (UsbOwnedDevice, 1); + g_atomic_ref_count_init (&owned_device->ref_count); + owned_device->device_id = g_strdup (device_id); + owned_device->fd = g_steal_fd (&fd); + + g_hash_table_insert (sender_info->owned_devices, + g_strdup (device_id), + g_steal_pointer (&owned_device)); +} + +static void +usb_sender_info_release_device (UsbSenderInfo *sender_info, + const char *device_id) +{ + g_assert (sender_info != NULL); + + if (!g_hash_table_remove (sender_info->owned_devices, device_id)) + g_warning ("Device %s not owned by %s", device_id, sender_info->sender_name); + +} + +static XdpPermission +usb_sender_info_get_device_permission (UsbSenderInfo *sender_info, + GUdevDevice *device) +{ + g_autofree char *permission_id = NULL; + + g_assert (G_UDEV_IS_DEVICE (device)); + + permission_id = unique_permission_id_for_device (device); + return xdp_get_permission_sync (xdp_app_info_get_id (sender_info->app_info), + PERMISSION_TABLE, permission_id); +} + +static void +usb_sender_info_set_device_permission (UsbSenderInfo *sender_info, + GUdevDevice *device, + XdpPermission permission) +{ + g_autofree char *permission_id = NULL; + + g_assert (G_UDEV_IS_DEVICE (device)); + + permission_id = unique_permission_id_for_device (device); + xdp_set_permission_sync (xdp_app_info_get_id (sender_info->app_info), + PERMISSION_TABLE, permission_id, permission); +} + +static gboolean +usb_sender_info_match_device (UsbSenderInfo *sender_info, + GUdevDevice *device) +{ + const char *device_subclass_str = NULL; + const char *device_class_str = NULL; + const char *product_id_str = NULL; + const char *vendor_id_str = NULL; + gboolean device_has_product_id = FALSE; + gboolean device_has_vendor_id = FALSE; + gboolean device_has_subclass = FALSE; + gboolean device_has_class = FALSE; + XdpPermission permission; + uint16_t device_product_id; + uint16_t device_vendor_id; + uint16_t device_subclass; + uint16_t device_class; + gboolean match = FALSE; + const GPtrArray *queries = NULL; + + permission = usb_sender_info_get_device_permission (sender_info, device); + if (permission == XDP_PERMISSION_NO) + return FALSE; + + vendor_id_str = g_udev_device_get_property (device, "ID_VENDOR_ID"); + if (vendor_id_str != NULL && xdp_validate_hex_uint16 (vendor_id_str, 4, &device_vendor_id)) + device_has_vendor_id = TRUE; + + product_id_str = g_udev_device_get_property (device, "ID_MODEL_ID"); + if (product_id_str != NULL && xdp_validate_hex_uint16 (product_id_str, 4, &device_product_id)) + device_has_product_id = TRUE; + + device_class_str = g_udev_device_get_sysfs_attr (device, "bDeviceClass"); + if (device_class_str != NULL && xdp_validate_hex_uint16 (device_class_str, 2, &device_class)) + device_has_class = TRUE; + + device_subclass_str = g_udev_device_get_sysfs_attr (device, "bDeviceSubclass"); + if (device_subclass_str != NULL && xdp_validate_hex_uint16 (device_subclass_str, 2, &device_subclass)) + device_has_subclass = TRUE; + + queries = xdp_app_info_get_usb_queries (sender_info->app_info); + for (size_t i = 0; queries && i < queries->len; i++) + { + XdpUsbQuery *query = g_ptr_array_index (queries, i); + gboolean query_matches = TRUE; + + if (!query) + { + g_debug ("query %ld is null", i); + continue; + } + + for (size_t j = 0; j < query->rules->len; j++) + { + XdpUsbRule *rule = g_ptr_array_index (query->rules, j); + + switch (rule->rule_type) + { + case XDP_USB_RULE_TYPE_ALL: + query_matches = TRUE; + break; + + case XDP_USB_RULE_TYPE_CLASS: + query_matches &= device_has_class && + device_class == rule->d.device_class.class; + + if (rule->d.device_class.type == XDP_USB_RULE_CLASS_TYPE_CLASS_SUBCLASS) + query_matches &= device_has_subclass && + device_subclass == rule->d.device_class.subclass; + + break; + + case XDP_USB_RULE_TYPE_DEVICE: + query_matches &= device_has_product_id && + device_product_id == rule->d.product.id; + break; + + case XDP_USB_RULE_TYPE_VENDOR: + query_matches &= device_has_vendor_id && + device_vendor_id == rule->d.vendor.id; + break; + + default: + g_assert_not_reached (); + } + } + + switch (query->query_type) + { + case XDP_USB_QUERY_TYPE_ENUMERABLE: + if (query_matches) + match = TRUE; + break; + + case XDP_USB_QUERY_TYPE_HIDDEN: + if (query_matches) + return FALSE; + break; + } + } + + return match; +} + +static void +xdp_usb_session_close (XdpSession *session) +{ + g_debug ("USB session '%s' closed", session->id); + + g_assert (g_hash_table_contains (usb->sessions, session)); + g_hash_table_remove (usb->sessions, session); +} + +static void +xdp_usb_session_dispose (GObject *object) +{ + XdpUsbSession *usb_session = XDP_USB_SESSION (object); + + g_clear_pointer (&usb_session->available_devices, g_hash_table_destroy); +} + +static void +xdp_usb_session_class_init (XdpUsbSessionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + XdpSessionClass *session_class = (XdpSessionClass*) klass; + + object_class->dispose = xdp_usb_session_dispose; + + session_class->close = xdp_usb_session_close; +} + +static void +xdp_usb_session_init (XdpUsbSession *session) +{ + session->available_devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); +} + +static XdpUsbSession * +xdp_usb_session_new (GDBusConnection *connection, + XdpCall *call, + GVariant *options, + GError **error) +{ + XdpSession *session = NULL; + + session = g_initable_new (XDP_TYPE_USB_SESSION, + NULL, error, + "connection", connection, + "sender", call->sender, + "app-id", xdp_app_info_get_id (call->app_info), + "token", lookup_session_token (options), + NULL); + if (!session) + return NULL; + + g_debug ("[usb] USB session '%s' created", session->id); + + return XDP_USB_SESSION (session); +} + +static GVariant * +gudev_device_to_variant (XdpUsb *self, + UsbSenderInfo *sender_info, + GUdevDevice *device) +{ + g_auto(GVariantDict) udev_properties_dict = G_VARIANT_DICT_INIT (NULL); + g_auto(GVariantDict) device_variant_dict = G_VARIANT_DICT_INIT (NULL); + g_autoptr(GUdevDevice) parent = NULL; + const char *device_file = NULL; + size_t n_added_properties = 0; + + static const char * const allowed_udev_properties[] = { + "ID_INPUT_JOYSTICK", + "ID_MODEL_ID", + "ID_MODEL_ENC", + "ID_MODEL_FROM_DATABASE", + "ID_REVISION", + "ID_SERIAL", + "ID_SERIAL_SHORT", + "ID_TYPE", + "ID_VENDOR_ENC", + "ID_VENDOR_ID", + "ID_VENDOR_FROM_DATABASE", + NULL, + }; + + if (!is_gudev_device_suitable (device)) + return NULL; + + parent = g_udev_device_get_parent (device); + if (parent != NULL && usb_sender_info_match_device (sender_info, parent)) + { + const char *parent_syspath = NULL; + const char *parent_id = NULL; + + parent_syspath = g_udev_device_get_sysfs_path (parent); + if (parent_syspath != NULL) + { + parent_id = g_hash_table_lookup (self->syspaths_to_ids, parent_syspath); + if (parent_id != NULL) + g_variant_dict_insert (&device_variant_dict, "parent", "s", parent_id); + } + } + + device_file = g_udev_device_get_device_file (device); + g_variant_dict_insert (&device_variant_dict, "device-file", "s", device_file); + + if (access (device_file, R_OK) != -1) + g_variant_dict_insert (&device_variant_dict, "readable", "b", TRUE); + if (access (device_file, W_OK) != -1) + g_variant_dict_insert (&device_variant_dict, "writable", "b", TRUE); + + for (size_t i = 0; allowed_udev_properties[i] != NULL; i++) + { + const char *property = g_udev_device_get_property (device, allowed_udev_properties[i]); + + if (!property) + continue; + + g_variant_dict_insert (&udev_properties_dict, allowed_udev_properties[i], "s", property); + n_added_properties++; + } + + if (n_added_properties > 0) + { + g_variant_dict_insert (&device_variant_dict, + "properties", + "@a{sv}", + g_variant_dict_end (&udev_properties_dict)); + } + + return g_variant_ref_sink (g_variant_dict_end (&device_variant_dict)); +} + +/* Register the device and create a unique ID for it */ +static char * +register_with_unique_usb_id (XdpUsb *self, + GUdevDevice *device) +{ + g_autofree char *id = NULL; + const char *syspath; + + g_assert (is_gudev_device_suitable (device)); + + syspath = g_udev_device_get_sysfs_path (device); + g_assert (syspath != NULL); + + do + { + g_clear_pointer (&id, g_free); + id = g_uuid_string_random (); + } + while (g_hash_table_contains (self->ids_to_devices, id)); + + g_debug ("Assigned unique ID %s to USB device %s", id, syspath); + + g_hash_table_insert (self->ids_to_devices, g_strdup (id), g_object_ref (device)); + g_hash_table_insert (self->syspaths_to_ids, g_strdup (syspath), g_strdup (id)); + + return g_steal_pointer (&id); +} + +static void +handle_session_event (XdpUsb *self, + XdpUsbSession *usb_session, + GUdevDevice *device, + const char *id, + const char *action, + gboolean removing) +{ + g_autoptr(GVariant) device_variant = NULL; + GVariantBuilder devices_builder; + UsbSenderInfo *sender_info; + XdpSession *session; + + g_assert (G_UDEV_IS_DEVICE (device)); + g_assert (g_strcmp0 (g_udev_device_get_subsystem (device), "usb") == 0); + + session = XDP_SESSION (usb_session); + sender_info = g_hash_table_lookup (self->sender_infos, session->sender); + g_assert (sender_info != NULL); + + /* We can't use usb_sender_info_match_device() when a device is being removed because, + * on removal, the only property the GUdevDevice has is its sysfs path. + * Check if this device was previously available to the USB session + * instead. */ + if ((removing && !g_hash_table_contains (usb_session->available_devices, id)) || + (!removing && !usb_sender_info_match_device (sender_info, device))) + return; + + g_variant_builder_init (&devices_builder, G_VARIANT_TYPE ("a(ssa{sv})")); + + device_variant = gudev_device_to_variant (self, sender_info, device); + g_variant_builder_add (&devices_builder, "(ss@a{sv})", action, id, device_variant); + + g_dbus_connection_emit_signal (session->connection, + session->sender, + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Usb", + "DeviceEvents", + g_variant_new ("(o@a(ssa{sv}))", + session->id, + g_variant_builder_end (&devices_builder)), + NULL); + + if (removing) + g_hash_table_remove (usb_session->available_devices, id); + else + g_hash_table_add (usb_session->available_devices, g_strdup (id)); +} + +static void +gudev_client_uevent_cb (GUdevClient *client, + const char *action, + GUdevDevice *device, + XdpUsb *self) +{ + static const char *supported_actions[] = { + "add", + "change", + "remove", + NULL, + }; + + g_autofree char *id = NULL; + GHashTableIter iter; + XdpUsbSession *usb_session; + const char *syspath = NULL; + gboolean removing; + + if (!g_strv_contains (supported_actions, action)) + return; + + if (!is_gudev_device_suitable (device)) + return; + + removing = g_str_equal (action, "remove"); + + if (g_str_equal (action, "add")) + { + id = register_with_unique_usb_id (self, device); + } + else + { + syspath = g_udev_device_get_sysfs_path (device); + + g_assert (syspath != NULL); + id = g_strdup (g_hash_table_lookup (self->syspaths_to_ids, syspath)); + } + + g_assert (id != NULL); + + /* Send event to all sessions that are allowed to handle it */ + g_hash_table_iter_init (&iter, self->sessions); + while (g_hash_table_iter_next (&iter, (gpointer *) &usb_session, NULL)) + handle_session_event (self, usb_session, device, id, action, removing); + + if (removing) + { + g_assert (syspath != NULL); + + g_debug ("Removing %s -> %s", id, syspath); + + /* The value of id is owned by syspaths_to_ids, so that must be removed *after* + the id is used for removal from ids_to_devices. */ + if (!g_hash_table_remove (self->ids_to_devices, id)) + g_critical ("Error removing USB device from ids_to_devices table"); + + if (!g_hash_table_remove (self->syspaths_to_ids, syspath)) + g_critical ("Error removing USB device from syspaths_to_ids table"); + } +} + +static void +send_initial_device_list (XdpUsb *self, + XdpUsbSession *usb_session, + XdpCall *call) +{ + /* Send initial list of devices the app has permission to see */ + g_autoptr(UsbSenderInfo) sender_info = NULL; + XdpSession *session = XDP_SESSION (usb_session); + GVariantBuilder devices_builder; + g_autoptr(GVariant) events = NULL; + GHashTableIter iter; + GUdevDevice *device; + const char *id; + gboolean has_devices = FALSE; + + g_debug ("[usb] Appending devices to CreateSession response"); + + g_variant_builder_init (&devices_builder, G_VARIANT_TYPE ("(oa(ssa{sv}))")); + g_variant_builder_add (&devices_builder, "o", session->id); + g_variant_builder_open (&devices_builder, G_VARIANT_TYPE ("a(ssa{sv})")); + + g_assert (self != NULL); + + sender_info = usb_sender_info_from_call (self, call); + + g_hash_table_iter_init (&iter, self->ids_to_devices); + while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &device)) + { + g_autoptr(GVariant) device_variant = NULL; + + g_assert (G_UDEV_IS_DEVICE (device)); + g_assert (g_strcmp0 (g_udev_device_get_subsystem (device), "usb") == 0); + + if (!usb_sender_info_match_device (sender_info, device)) + continue; + + device_variant = gudev_device_to_variant (self, sender_info, device); + /* NULL mean the device isn't suitable */ + if (device_variant == NULL) + continue; + + g_variant_builder_add (&devices_builder, "(ss@a{sv})", "add", id, device_variant); + + g_hash_table_add (usb_session->available_devices, g_strdup (id)); + + has_devices = TRUE; + } + + g_variant_builder_close (&devices_builder); + events = g_variant_ref_sink (g_variant_builder_end (&devices_builder)); + + if (!has_devices) + return; + + g_dbus_connection_emit_signal (session->connection, + session->sender, + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Usb", + "DeviceEvents", + events, + NULL); +} + +static gboolean +handle_create_session (XdpDbusUsb *object, + GDBusMethodInvocation *invocation, + GVariant *arg_options) +{ + g_autoptr(GVariant) options = NULL; + g_autoptr(GError) error = NULL; + GDBusConnection *connection; + GVariantBuilder options_builder; + XdpUsbSession *usb_session; + XdpPermission permission; + XdpSession *session; + XdpCall *call; + XdpUsb *self; + + static const XdpOptionKey usb_create_session_options[] = { + { "session_handle_token", G_VARIANT_TYPE_STRING, NULL }, + }; + + self = XDP_USB (object); + call = xdp_call_from_invocation (invocation); + + g_debug ("[usb] Handling CreateSession"); + + permission = xdp_get_permission_sync (xdp_app_info_get_id (call->app_info), + PERMISSION_TABLE, + PERMISSION_ID); + if (permission == XDP_PERMISSION_NO) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "Not allowed to create USB sessions"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); + if (!xdp_filter_options (arg_options, + &options_builder, + usb_create_session_options, + G_N_ELEMENTS (usb_create_session_options), + &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); + + connection = g_dbus_method_invocation_get_connection (invocation); + usb_session = xdp_usb_session_new (connection, call, options, &error); + if (!usb_session) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + session = XDP_SESSION (usb_session); + if (!xdp_session_export (session, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + xdp_session_close (session, FALSE); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + xdp_session_register (session); + + g_debug ("New USB session registered: %s", session->id); + g_hash_table_add (self->sessions, usb_session); + + xdp_dbus_usb_complete_create_session (object, invocation, session->id); + + send_initial_device_list (self, usb_session, call); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static GVariant * +list_permitted_devices (XdpUsb *self, + XdpCall *call) +{ + g_autoptr(UsbSenderInfo) sender_info = NULL; + GVariantBuilder builder; + GHashTableIter iter; + GUdevDevice *device; + const char *id; + + sender_info = usb_sender_info_from_call (self, call); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sa{sv})")); + + g_hash_table_iter_init (&iter, self->ids_to_devices); + while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &device)) + { + g_assert (G_UDEV_IS_DEVICE (device)); + g_assert (g_strcmp0 (g_udev_device_get_subsystem (device), "usb") == 0); + + if (usb_sender_info_match_device (sender_info, device)) + { + g_autoptr(GVariant) device_variant = gudev_device_to_variant (self, sender_info, device); + if (device_variant == NULL) + continue; + + g_variant_builder_add (&builder, "(s@a{sv})", id, device_variant); + } + } + + return g_variant_ref_sink (g_variant_builder_end (&builder)); +} + +/* List devices the app has permission */ +static gboolean +handle_enumerate_devices (XdpDbusUsb *object, + GDBusMethodInvocation *invocation, + GVariant *arg_options) +{ + g_autoptr(GVariant) options = NULL; + g_autoptr(GVariant) devices = NULL; + g_autoptr(GError) error = NULL; + GVariantBuilder options_builder; + XdpPermission permission; + XdpCall *call; + XdpUsb *self; + + static const XdpOptionKey usb_enumerate_devices_options[] = { + }; + + self = XDP_USB (object); + call = xdp_call_from_invocation (invocation); + + permission = xdp_get_permission_sync (xdp_app_info_get_id (call->app_info), + PERMISSION_TABLE, + PERMISSION_ID); + + if (permission == XDP_PERMISSION_NO) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "Not allowed to enumerate devices"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); + if (!xdp_filter_options (arg_options, &options_builder, + usb_enumerate_devices_options, + G_N_ELEMENTS (usb_enumerate_devices_options), + &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); + + devices = list_permitted_devices(self, call); + + xdp_dbus_usb_complete_enumerate_devices (object, invocation, devices); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +usb_acquire_devices_cb (GObject *source_object, + GAsyncResult *result, + gpointer data) +{ + XdgDesktopPortalResponseEnum response; + g_autoptr(UsbSenderInfo) sender_info = NULL; + g_autoptr(GVariantIter) devices_iter = NULL; + g_auto(GVariantBuilder) results_builder; + g_autoptr (GVariant) results = NULL; + g_autoptr(XdpRequest) request = data; + g_autoptr(GError) error = NULL; + GVariant *options; + const char *device_id; + + REQUEST_AUTOLOCK (request); + + response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER; + sender_info = usb_sender_info_from_request (usb, request); + + g_assert (sender_info != NULL); + + g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); + + if (!xdp_dbus_impl_usb_call_acquire_devices_finish (usb_impl, &response, &results, result, &error)) + { + response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER; + g_dbus_error_strip_remote_error (error); + goto out; + } + + /* TODO: check if the list of devices that the backend reported is strictly + * equal or a subset of the devices the app requested. */ + + /* TODO: check if we're strictly equal or downgrading the "writable" option */ + + if (!g_variant_lookup (results, "devices", "a(sa{sv})", &devices_iter)) + goto out; + + if (response == XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS) + { + g_autoptr(GPtrArray) pending_devices = + g_ptr_array_new_full (g_variant_iter_n_children (devices_iter), + (GDestroyNotify) usb_device_acquire_data_free); + while (g_variant_iter_next (devices_iter, "(&s@a{sv})", &device_id, &options)) + { + g_autoptr(UsbDeviceAcquireData) access_data = NULL; + GUdevDevice *device; + gboolean writable; + + device = g_hash_table_lookup (usb->ids_to_devices, device_id); + if (!device) + continue; + + if (!g_variant_lookup (options, "writable", "b", &writable)) + writable = FALSE; + + access_data = g_new0 (UsbDeviceAcquireData, 1); + access_data->device_id = g_strdup (device_id); + access_data->writable = writable; + + g_ptr_array_add (pending_devices, g_steal_pointer (&access_data)); + + usb_sender_info_set_device_permission (sender_info, device, XDP_PERMISSION_YES); + + g_clear_pointer (&options, g_variant_unref); + } + g_hash_table_insert (sender_info->pending_devices, + g_strdup (xdp_request_get_object_path (request)), + g_steal_pointer (&pending_devices)); + } + else if (response == XDG_DESKTOP_PORTAL_RESPONSE_CANCELLED) + { + g_hash_table_remove (sender_info->pending_devices, + xdp_request_get_object_path (request)); + } + +out: + if (request->exported) + { + xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), + response, + g_variant_builder_end (&results_builder)); + xdp_request_unexport (request); + } +} + +static gboolean +filter_access_devices (XdpUsb *self, + UsbSenderInfo *sender_info, + GVariant *devices, + GVariant **out_filtered_devices, + GError **out_error) +{ + GVariantBuilder filtered_devices_builder; + GVariantIter *device_options_iter; + GVariantIter devices_iter; + const char *device_id; + size_t n_devices; + + static const XdpOptionKey usb_device_options[] = { + { "writable", G_VARIANT_TYPE_BOOLEAN, NULL }, + }; + + g_assert (self != NULL); + g_assert (sender_info != NULL); + g_assert (devices != NULL); + g_assert (out_filtered_devices != NULL && *out_filtered_devices == NULL); + g_assert (out_error != NULL && *out_error == NULL); + + n_devices = g_variant_iter_init (&devices_iter, devices); + + if (n_devices == 0) + { + g_set_error (out_error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "No devices in the devices array"); + return FALSE; + } + + g_variant_builder_init (&filtered_devices_builder, G_VARIANT_TYPE ("a(sa{sv}a{sv})")); + + while (g_variant_iter_next (&devices_iter, + "(&sa{sv})", + &device_id, + &device_options_iter)) + { + g_autoptr(GVariantIter) owned_deviced_options_iter = device_options_iter; + g_autoptr(GVariant) device_variant = NULL; + GVariantDict device_options_dict; + GUdevDevice *device; + GVariant *device_option_value; + const char *device_option; + + device = g_hash_table_lookup (self->ids_to_devices, device_id); + + if (!device) + { + g_set_error (out_error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Device %s not available", + device_id); + return FALSE; + } + + g_assert (G_UDEV_IS_DEVICE (device)); + g_assert (g_strcmp0 (g_udev_device_get_subsystem (device), "usb") == 0); + + /* Can the app even request this device? */ + if (!usb_sender_info_match_device (sender_info, device)) + { + g_set_error (out_error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "Access to device %s is not allowed", + device_id); + return FALSE; + } + + g_variant_dict_init (&device_options_dict, NULL); + + while (g_variant_iter_next (device_options_iter, + "{&sv}", + &device_option, + &device_option_value)) + { + for (size_t i = 0; i < G_N_ELEMENTS (usb_device_options); i++) + { + if (g_strcmp0 (device_option, usb_device_options[i].key) != 0) + continue; + + if (!g_variant_is_of_type (device_option_value, usb_device_options[i].type)) + { + g_set_error (out_error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "Invalid type for option '%s'", + device_option); + g_variant_builder_clear (&filtered_devices_builder); + g_variant_dict_clear (&device_options_dict); + g_clear_pointer (&device_option_value, g_variant_unref); + return FALSE; + } + + g_variant_dict_insert_value (&device_options_dict, device_option, device_option_value); + + g_clear_pointer (&device_option_value, g_variant_unref); + } + } + + device_variant = gudev_device_to_variant (self, sender_info, device); + + g_variant_builder_add (&filtered_devices_builder, + "(s@a{sv}@a{sv})", + device_id, + device_variant, + g_variant_dict_end (&device_options_dict)); + } + + *out_filtered_devices = + g_variant_ref_sink (g_variant_builder_end (&filtered_devices_builder)); + return TRUE; +} + +static gboolean +handle_acquire_devices (XdpDbusUsb *object, + GDBusMethodInvocation *invocation, + const char *arg_parent_window, + GVariant *arg_devices, + GVariant *arg_options) +{ + g_autoptr(XdpDbusImplRequest) impl_request = NULL; + g_autoptr(UsbSenderInfo) sender_info = NULL; + g_autoptr(GVariant) filtered_devices = NULL; + g_autoptr(GVariant) options = NULL; + g_autoptr(GError) error = NULL; + GVariantBuilder options_builder; + XdpPermission permission; + XdpRequest *request; + XdpUsb *self; + + static const XdpOptionKey usb_acquire_devices_options[] = { + }; + + self = XDP_USB (object); + request = xdp_request_from_invocation (invocation); + + g_debug ("[usb] Handling AccessDevices"); + + REQUEST_AUTOLOCK (request); + + permission = xdp_get_permission_sync (xdp_app_info_get_id (request->app_info), + PERMISSION_TABLE, + PERMISSION_ID); + if (permission == XDP_PERMISSION_NO) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "Not allowed to create USB sessions"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + impl_request = xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (usb_impl)), + G_DBUS_PROXY_FLAGS_NONE, + g_dbus_proxy_get_name (G_DBUS_PROXY (usb_impl)), + request->id, + NULL, + &error); + if (!impl_request) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); + if (!xdp_filter_options (arg_options, + &options_builder, + usb_acquire_devices_options, + G_N_ELEMENTS (usb_acquire_devices_options), + &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); + + sender_info = usb_sender_info_from_request (self, request); + g_assert (sender_info != NULL); + + /* Validate devices */ + if (!filter_access_devices (self, sender_info, arg_devices, &filtered_devices, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + xdp_request_set_impl_request (request, impl_request); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); + + xdp_dbus_impl_usb_call_acquire_devices (usb_impl, + request->id, + arg_parent_window, + xdp_app_info_get_id (request->app_info), + filtered_devices, + options, + NULL, + usb_acquire_devices_cb, + g_object_ref (request)); + + xdp_dbus_usb_complete_acquire_devices (object, invocation, request->id); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_finish_acquire_devices (XdpDbusUsb *object, + GDBusMethodInvocation *invocation, + const char *object_path, + GVariant *arg_options) +{ + g_autoptr(UsbSenderInfo) sender_info = NULL; + g_autoptr(GUnixFDList) fds = NULL; + GVariantBuilder results_builder; + XdpPermission permission; + uint32_t accessed_devices; + gboolean finished; + GPtrArray *pending_devices = NULL; + XdpCall *call; + XdpUsb *self; + + self = XDP_USB (object); + call = xdp_call_from_invocation (invocation); + + g_debug ("[usb] Handling FinishAccessDevices"); + + sender_info = usb_sender_info_from_call (self, call); + + pending_devices = g_hash_table_lookup (sender_info->pending_devices, object_path); + if (pending_devices == NULL) + { + /* We don't have the request in the pending_devices. Either it has + * been cancelled or it simply doesn't exist. + */ + g_hash_table_remove (sender_info->pending_devices, object_path); + + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "There is no device being acquired"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + permission = xdp_get_permission_sync (xdp_app_info_get_id (call->app_info), + PERMISSION_TABLE, + PERMISSION_ID); + if (permission == XDP_PERMISSION_NO) + { + /* If permission was revoked in between D-Bus calls, reset state */ + g_hash_table_remove (sender_info->pending_devices, object_path); + + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, + "Not allowed to access USB devices"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + /* We should never trigger these asserts. */ + g_assert (pending_devices != NULL); + + fds = g_unix_fd_list_new (); + + g_variant_builder_init (&results_builder, G_VARIANT_TYPE ("a(sa{sv})")); + + accessed_devices = 0; + while (accessed_devices < MAX_DEVICES && + pending_devices->len > 0) + { + g_autoptr(UsbDeviceAcquireData) access_data = NULL; + g_autoptr(GError) error = NULL; + GVariantDict dict; + UsbOwnedDevice *owned_device = NULL; + g_autofd int fd = -1; + int fd_index; + + g_variant_dict_init (&dict, NULL); + + access_data = g_ptr_array_steal_index (pending_devices, 0); + + /* Check we haven't already acquired the device */ + owned_device = g_hash_table_lookup (sender_info->owned_devices, access_data->device_id); + if (!owned_device) + { + const char *device_file; + GUdevDevice *device; + + device = g_hash_table_lookup (self->ids_to_devices, access_data->device_id); + + if (!device) + { + g_variant_dict_insert (&dict, "success", "b", FALSE); + g_variant_dict_insert (&dict, "error", "s", _("Device not available")); + g_variant_builder_add (&results_builder, "(s@a{sv})", + access_data->device_id, + g_variant_dict_end (&dict)); + continue; + } + + device_file = g_udev_device_get_device_file (device); + g_assert (device_file != NULL); + + /* Can the app even request this device? */ + if (!usb_sender_info_match_device (sender_info, device)) + { + g_variant_dict_insert (&dict, "success", "b", FALSE); + g_variant_dict_insert (&dict, "error", "s", _("Not allowed")); + g_variant_builder_add (&results_builder, "(s@a{sv})", + access_data->device_id, + g_variant_dict_end (&dict)); + continue; + } + + fd = open (device_file, access_data->writable ? O_RDWR : O_RDONLY); + if (fd == -1) + { + g_variant_dict_insert (&dict, "success", "b", FALSE); + g_variant_dict_insert (&dict, "error", "s", g_strerror (errno)); + g_variant_builder_add (&results_builder, "(s@a{sv})", + access_data->device_id, + g_variant_dict_end (&dict)); + continue; + } + fd_index = g_unix_fd_list_append (fds, fd, &error); + } + else + { + /* If we have already acquired the device, just return the fd again */ + fd_index = g_unix_fd_list_append (fds, owned_device->fd, &error); + } + + if (error) + { + g_variant_dict_insert (&dict, "success", "b", FALSE); + g_variant_dict_insert (&dict, "error", "s", error->message); + g_variant_builder_add (&results_builder, "(s@a{sv})", + access_data->device_id, + g_variant_dict_end (&dict)); + continue; + } + + /* This sender now owns this device. Either create a new one + * or ref the existing one. + */ + if (!owned_device) + { + usb_sender_info_acquire_device (sender_info, + access_data->device_id, + g_steal_fd (&fd)); + } + else + { + usb_owned_device_ref (owned_device); + } + + g_variant_dict_insert (&dict, "success", "b", TRUE); + g_variant_dict_insert (&dict, "fd", "h", fd_index); + g_variant_builder_add (&results_builder, "(s@a{sv})", + access_data->device_id, + g_variant_dict_end (&dict)); + + accessed_devices++; + } + + finished = pending_devices->len == 0; + + if (finished) + { + g_hash_table_remove (sender_info->pending_devices, object_path); + } + + g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, + g_variant_new ("(@a(sa{sv})b)", + g_variant_builder_end (&results_builder), + finished), + fds); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_release_devices (XdpDbusUsb *object, + GDBusMethodInvocation *invocation, + const char * const *arg_devices, + GVariant *arg_options) +{ + g_autoptr(UsbSenderInfo) sender_info = NULL; + g_autoptr(GVariant) options = NULL; + g_autoptr(GError) error = NULL; + GVariantBuilder options_builder; + XdpCall *call; + XdpUsb *self; + + static const XdpOptionKey usb_release_devices_options[] = { + }; + + self = XDP_USB (object); + call = xdp_call_from_invocation (invocation); + + g_debug ("[usb] Handling ReleaseDevices"); + + g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); + if (!xdp_filter_options (arg_options, + &options_builder, + usb_release_devices_options, + G_N_ELEMENTS (usb_release_devices_options), + &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); + + sender_info = usb_sender_info_from_call (self, call); + g_assert (sender_info != NULL); + + for (size_t i = 0; arg_devices && arg_devices[i]; i++) + usb_sender_info_release_device (sender_info, arg_devices[i]); + + xdp_dbus_usb_complete_release_devices (object, invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +xdp_usb_iface_init (XdpDbusUsbIface *iface) +{ + iface->handle_create_session = handle_create_session; + iface->handle_enumerate_devices = handle_enumerate_devices; + iface->handle_acquire_devices = handle_acquire_devices; + iface->handle_finish_acquire_devices = handle_finish_acquire_devices; + iface->handle_release_devices = handle_release_devices; +} + +static void +xdp_usb_dispose (GObject *object) +{ + XdpUsb *self = XDP_USB (object); + + g_clear_pointer (&self->ids_to_devices, g_hash_table_unref); + g_clear_pointer (&self->syspaths_to_ids, g_hash_table_unref); + g_clear_pointer (&self->sessions, g_hash_table_unref); + + g_clear_object (&self->gudev_client); +} + +static void +xdp_usb_class_init (XdpUsbClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = xdp_usb_dispose; +} + +static void +xdp_usb_init (XdpUsb *self) +{ + g_autolist(GUdevDevice) devices = NULL; + static const char * const subsystems[] = { + "usb", + NULL, + }; + + g_debug ("[usb] Initializing USB portal"); + + xdp_dbus_usb_set_version (XDP_DBUS_USB (self), 1); + + self->ids_to_devices = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); + self->syspaths_to_ids = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + self->sessions = g_hash_table_new (g_direct_hash, g_direct_equal); + self->sender_infos = + g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) usb_sender_info_unref); + + self->gudev_client = g_udev_client_new (subsystems); + g_signal_connect (self->gudev_client, + "uevent", + G_CALLBACK (gudev_client_uevent_cb), + self); + + /* Initialize devices */ + devices = g_udev_client_query_by_subsystem (self->gudev_client, "usb"); + for (GList *l = devices; l; l = l->next) + { + g_autofree char *id = NULL; + GUdevDevice *device = l->data; + + if (!is_gudev_device_suitable (device)) + continue; + + id = register_with_unique_usb_id (self, device); + } +} + +static void +peer_died_cb (const char *sender) +{ + if (usb && g_hash_table_remove (usb->sender_infos, sender)) + g_debug ("Removed sender %s", sender); +} + +GDBusInterfaceSkeleton * +xdp_usb_create (GDBusConnection *connection, + const char *dbus_name) +{ + g_autoptr(GError) error = NULL; + + usb_impl = xdp_dbus_impl_usb_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + dbus_name, + DESKTOP_PORTAL_OBJECT_PATH, + NULL, + &error); + if (usb_impl == NULL) + { + g_warning ("Failed to create USB proxy: %s", error->message); + return NULL; + } + + xdp_connection_track_name_owners (connection, peer_died_cb); + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (usb_impl), G_MAXINT); + + g_assert (usb_impl != NULL); + g_assert (usb == NULL); + + usb = g_object_new (xdp_usb_get_type (), NULL); + + return G_DBUS_INTERFACE_SKELETON (usb); +} diff --git a/src/call.h b/src/usb.h similarity index 68% rename from src/call.h rename to src/usb.h index 1ff8b31..6363828 100644 --- a/src/call.h +++ b/src/usb.h @@ -1,5 +1,6 @@ /* - * Copyright © 2018 Red Hat, Inc + * Copyright © 2023 GNOME Foundation Inc. + * 2020 Endless OS Foundation LLC * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -14,20 +15,14 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * + * Authors: + * Georges Basile Stavracas Neto + * Ryan Gonzalez */ #pragma once #include -#include "xdp-utils.h" -typedef struct _Call -{ - XdpAppInfo *app_info; - char *sender; -} Call; - -void call_init_invocation (GDBusMethodInvocation *invocation, - XdpAppInfo *app_info); - -Call *call_from_invocation (GDBusMethodInvocation *invocation); +GDBusInterfaceSkeleton * xdp_usb_create (GDBusConnection *connection, + const char *dbus_name); diff --git a/src/validate-icon.c b/src/validate-icon.c index b8c9bda..4440543 100644 --- a/src/validate-icon.c +++ b/src/validate-icon.c @@ -16,6 +16,7 @@ * * Authors: * Matthias Clasen + * Julian Sparber */ /* The canonical copy of this file is in: @@ -27,88 +28,174 @@ #include #include #include +#include #include +#include "xdp-utils.h" + #ifdef __FreeBSD__ #define execvpe exect #endif #define ICON_VALIDATOR_GROUP "Icon Validator" +typedef struct +{ + const char *name; + size_t max_icon_size; + size_t max_svg_icon_size; + size_t max_file_size; +} XdpValidatorRuleset; + +static const XdpValidatorRuleset rulesets[] = +{ + { + .name = "desktop", + .max_icon_size = 512, + .max_svg_icon_size = 4096, + .max_file_size = 1024 * 1024 * 4 /* 4MB */, + }, + { + .name = "notification", + .max_icon_size = 512, + .max_svg_icon_size = 4096, + .max_file_size = 1024 * 1024 * 4 /* 4MB */, + }, +}; + +static const XdpValidatorRuleset *ruleset = NULL; +static gboolean opt_sandbox; +static char *opt_path = NULL; +static int opt_fd = -1; + +static gboolean +option_validator_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + for (size_t i = 0; i < G_N_ELEMENTS (rulesets); i++) + { + if (g_strcmp0 (value, rulesets[i].name) == 0) + { + ruleset = &rulesets[i]; + return TRUE; + } + } + + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Invalid ruleset '%s'. Accepted values are: desktop, notification", + value); + return FALSE; +} + +static GOptionEntry entries[] = { + { "sandbox", 0, 0, G_OPTION_ARG_NONE, &opt_sandbox, "Run in a sandbox", NULL }, + { "path", 0, 0, G_OPTION_ARG_FILENAME, &opt_path, "Read icon data from given file path. Required to be from a trusted source.", "PATH" }, + { "fd", 0, 0, G_OPTION_ARG_INT, &opt_fd, "Read icon data from given file descriptor. Required to be from a trusted source or to be sealed", "FD" }, + { "ruleset", 0, 0, G_OPTION_ARG_CALLBACK, &option_validator_cb, "The icon validator ruleset to apply. Accepted values: desktop, notification", "RULESET" }, + { NULL } +}; + static int -validate_icon (const char *arg_width, - const char *arg_height, - const char *filename) +validate_icon (int input_fd) { - GdkPixbufFormat *format; - int max_width, max_height; - int width, height; - g_autofree char *name = NULL; const char *allowed_formats[] = { "png", "jpeg", "svg", NULL }; - g_autoptr(GdkPixbuf) pixbuf = NULL; + g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; + GdkPixbufFormat *format; g_autoptr(GKeyFile) key_file = NULL; g_autofree char *key_file_data = NULL; + g_autoptr(GdkPixbufLoader) loader = NULL; + g_autoptr(GMappedFile) mapped = NULL; + int max_size, width, height; + g_autofree char *name = NULL; + GdkPixbuf *pixbuf; + + g_assert (ruleset != NULL); + + /* Ensure that we read from the beginning of the file */ + lseek (input_fd, 0, SEEK_SET); - format = gdk_pixbuf_get_file_info (filename, &width, &height); - if (format == NULL) + mapped = g_mapped_file_new_from_fd (input_fd, FALSE, &error); + if (!mapped) { - g_printerr ("Format not recognized\n"); + g_printerr ("Failed to create mapped file for image: %s\n", error->message); return 1; } - name = gdk_pixbuf_format_get_name (format); - if (!g_strv_contains (allowed_formats, name)) + bytes = g_mapped_file_get_bytes (mapped); + + if (g_bytes_get_size (bytes) == 0) { - g_printerr ("Format %s not accepted\n", name); + g_printerr ("Image is 0 bytes\n"); return 1; } - if (!g_str_equal (name, "svg")) + if (g_bytes_get_size (bytes) > ruleset->max_file_size) { - max_width = g_ascii_strtoll (arg_width, NULL, 10); - if (max_width < 16 || max_width > 4096) - { - g_printerr ("Bad width limit: %s\n", arg_width); - return 1; - } - - max_height = g_ascii_strtoll (arg_height, NULL, 10); - if (max_height < 16 || max_height > 4096) - { - g_printerr ("Bad height limit: %s\n", arg_height); - return 1; - } + g_printerr ("Image is bigger then the allowed size\n"); + return 1; } - else + + loader = gdk_pixbuf_loader_new (); + + if (!gdk_pixbuf_loader_write_bytes (loader, bytes, &error)) { - /* Sanity check for vector files */ - max_height = max_width = 4096; + g_printerr ("Failed to load image: %s\n", error->message); + gdk_pixbuf_loader_close (loader, NULL); + return 1; } - if (width > max_width || height > max_height) + if (!gdk_pixbuf_loader_close (loader, &error)) { - g_printerr ("Image too large (%dx%d). Max. size %dx%d\n", width, height, max_width, max_height); + g_printerr ("Failed to load image: %s\n", error->message); return 1; } - pixbuf = gdk_pixbuf_new_from_file (filename, &error); - if (pixbuf == NULL) + pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); + if (!pixbuf) { g_printerr ("Failed to load image: %s\n", error->message); return 1; } + format = gdk_pixbuf_loader_get_format (loader); + if (!format) + { + g_printerr ("Image format not recognized\n"); + return 1; + } + + name = gdk_pixbuf_format_get_name (format); + if (!g_strv_contains (allowed_formats, name)) + { + g_printerr ("Image format %s not accepted\n", name); + return 1; + } + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + if (width != height) { - g_printerr ("Expected a square icon but got: %dx%d\n", width, height); + g_printerr ("Expected a square image but got: %dx%d\n", width, height); + return 1; + } + + /* Sanity check for vector files */ + max_size = g_str_equal (name, "svg") ? ruleset->max_svg_icon_size : ruleset->max_icon_size; + + /* The icon is a square so we only need to check one side */ + if (width > max_size) + { + g_printerr ("Image too large (%dx%d). Max. size %dx%d\n", width, height, max_size, max_size); return 1; } /* Print the format and size for consumption by (at least) the dynamic - * launcher portal. xdg-desktop-portal has a copy of this file. Use a - * GKeyFile so the output can be easily extended in the future in a backwards - * compatible way. + * launcher portal. Use a GKeyFile so the output can be easily extended + * in the future in a backwards compatible way. */ key_file = g_key_file_new (); g_key_file_set_string (key_file, ICON_VALIDATOR_GROUP, "format", name); @@ -165,16 +252,16 @@ flatpak_get_bwrap (void) } static int -rerun_in_sandbox (const char *arg_width, - const char *arg_height, - const char *filename) +rerun_in_sandbox (int input_fd) { const char * const usrmerged_dirs[] = { "bin", "lib32", "lib64", "lib", "sbin" }; - int i; g_autoptr(GPtrArray) args = g_ptr_array_new_with_free_func (g_free); + g_autofree char* arg_input_fd = NULL; char validate_icon[PATH_MAX + 1]; ssize_t symlink_size; + g_assert (ruleset != NULL); + symlink_size = readlink ("/proc/self/exe", validate_icon, sizeof (validate_icon) - 1); if (symlink_size < 0 || (size_t) symlink_size >= sizeof (validate_icon)) { @@ -189,13 +276,16 @@ rerun_in_sandbox (const char *arg_width, "--unshare-ipc", "--unshare-net", "--unshare-pid", + "--tmpfs", "/tmp", + "--proc", "/proc", + "--dev", "/dev", "--ro-bind", "/usr", "/usr", "--ro-bind-try", "/etc/ld.so.cache", "/etc/ld.so.cache", "--ro-bind", validate_icon, validate_icon, NULL); /* These directories might be symlinks into /usr/... */ - for (i = 0; i < G_N_ELEMENTS (usrmerged_dirs); i++) + for (size_t i = 0; i < G_N_ELEMENTS (usrmerged_dirs); i++) { g_autofree char *absolute_dir = g_strdup_printf ("/%s", usrmerged_dirs[i]); @@ -219,14 +309,10 @@ rerun_in_sandbox (const char *arg_width, } add_args (args, - "--tmpfs", "/tmp", - "--proc", "/proc", - "--dev", "/dev", "--chdir", "/", "--setenv", "GIO_USE_VFS", "local", "--unsetenv", "TMPDIR", "--die-with-parent", - "--ro-bind", filename, filename, NULL); if (g_getenv ("G_MESSAGES_DEBUG")) @@ -234,14 +320,14 @@ rerun_in_sandbox (const char *arg_width, if (g_getenv ("G_MESSAGES_PREFIXED")) add_args (args, "--setenv", "G_MESSAGES_PREFIXED", g_getenv ("G_MESSAGES_PREFIXED"), NULL); - add_args (args, validate_icon, arg_width, arg_height, filename, NULL); + arg_input_fd = g_strdup_printf ("%d", input_fd); + add_args (args, + validate_icon, + "--fd", arg_input_fd, + "--ruleset", ruleset->name, + NULL); g_ptr_array_add (args, NULL); - { - g_autofree char *cmdline = g_strjoinv (" ", (char **) args->pdata); - g_debug ("Icon validation: Spawning %s", cmdline); - } - execvpe (flatpak_get_bwrap (), (char **) args->pdata, NULL); /* If we get here, then execvpe() failed. */ g_printerr ("Icon validation: execvpe %s: %s\n", flatpak_get_bwrap (), g_strerror (errno)); @@ -249,20 +335,14 @@ rerun_in_sandbox (const char *arg_width, } #endif -static gboolean opt_sandbox; - -static GOptionEntry entries[] = { - { "sandbox", 0, 0, G_OPTION_ARG_NONE, &opt_sandbox, "Run in a sandbox", NULL }, - { NULL } -}; - int main (int argc, char *argv[]) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GError) error = NULL; + g_autofd int fd_path = -1; - context = g_option_context_new ("WIDTH HEIGHT PATH"); + context = g_option_context_new (NULL); g_option_context_add_main_entries (context, entries, NULL); if (!g_option_context_parse (context, &argc, &argv, &error)) { @@ -270,16 +350,40 @@ main (int argc, char *argv[]) return 1; } - if (argc != 4) + if (ruleset == NULL) { - g_printerr ("Usage: %s [OPTION…] WIDTH HEIGHT PATH\n", argv[0]); + g_printerr ("Error: A ruleset must be given with --ruleset\n"); + return 1; + } + + if (opt_path != NULL && opt_fd != -1) + { + g_printerr ("Error: Only --path or --fd can be given\n"); + return 1; + } + + if (opt_path) + { + opt_fd = fd_path = g_open (opt_path, O_RDONLY, 0); + if (opt_fd == -1) + { + g_printerr ("Error: Couldn't open file\n"); + return 1; + } + } + else if (opt_fd == -1) + { + g_autofree char *help = NULL; + + help = g_option_context_get_help (context, TRUE, NULL); + g_printerr ("Error: Either --path or --fd needs to be given\n\n%s", help); return 1; } #ifdef HELPER if (opt_sandbox) - return rerun_in_sandbox (argv[1], argv[2], argv[3]); + return rerun_in_sandbox (opt_fd); else #endif - return validate_icon (argv[1], argv[2], argv[3]); + return validate_icon (opt_fd); } diff --git a/src/validate-sound.c b/src/validate-sound.c new file mode 100644 index 0000000..db2b2e3 --- /dev/null +++ b/src/validate-sound.c @@ -0,0 +1,369 @@ +/* + * Copyright © 2018 Red Hat, Inc + * Copyright © 2024 GNOME Foundation Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Matthias Clasen + * Julian Sparber + */ + +/* This is based on src/validate-icon.c */ + +#include +#include +#include +#include +#include +#include + +#ifdef __FreeBSD__ +#define execvpe exect +#endif + +#define SOUND_VALIDATOR_GROUP "Sound Validator" + +static int +validate_sound (int input_fd) +{ + g_autoptr(GKeyFile) key_file = NULL; + g_autofree char *key_file_data = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GstDiscoverer) discoverer = NULL; + g_autoptr(GstDiscovererInfo) info = NULL; + g_autoptr(GstDiscovererStreamInfo) audio_info = NULL; + g_autoptr(GstDiscovererStreamInfo) stream_info = NULL; + g_autoptr(GstDiscovererStreamInfo) stream_next = NULL; + GstDiscovererResult result; + const gchar *format = NULL; + g_autofree gchar *uri = NULL; + + gst_init (NULL, NULL); + + discoverer = gst_discoverer_new (GST_SECOND, &error); + + if (!discoverer) + { + g_printerr ("validate-sound: Failed to create gstreamer discoverer: %s\n", error->message); + return 1; + } + + uri = g_strdup_printf ("file:///proc/self/fd/%d", input_fd); + info = gst_discoverer_discover_uri (discoverer, uri, &error); + result = gst_discoverer_info_get_result (info); + + switch (result) + { + case GST_DISCOVERER_URI_INVALID: + g_assert_not_reached (); + return 1; + case GST_DISCOVERER_ERROR: + g_printerr ("validate-sound: Couldn't discover media type: %s\n", error->message); + return 1; + case GST_DISCOVERER_TIMEOUT: + g_printerr ("validate-sound: Couldn't discover media type: Timeout\n"); + return 1; + case GST_DISCOVERER_BUSY: + g_printerr ("validate-sound: Couldn't discover media type: Busy\n"); + return 1; + case GST_DISCOVERER_MISSING_PLUGINS: + { + g_autofree char *str = NULL; + + str = g_strjoinv ("\n", + (char **) gst_discoverer_info_get_missing_elements_installer_details (info)); + + g_printerr ("validate-sound: Couldn't discover media type: Missing plugins: %s\n", str); + return 1; + } + case GST_DISCOVERER_OK: + break; + } + + stream_info = gst_discoverer_info_get_stream_info (info); + if (!stream_info) + { + g_printerr ("validate-sound: Contains a invalid stream\n"); + return 1; + } + + stream_next = gst_discoverer_stream_info_get_next (stream_info); + if (stream_next) + { + g_printerr ("validate-sound: Only a single stream is allowed\n"); + return 1; + } + + if (GST_IS_DISCOVERER_CONTAINER_INFO (stream_info)) + { + g_autoptr(GList) streams = NULL; + g_autoptr(GstCaps) container_caps = NULL; + GstStructure *structure = NULL; + + container_caps = gst_discoverer_stream_info_get_caps (stream_info); + structure = gst_caps_get_structure (container_caps, 0); + + if (!gst_caps_is_fixed (container_caps) || gst_caps_get_size (container_caps) != 1) + { + g_printerr ("validate-sound: The media format is to complex\n"); + return 1; + } + + if (!gst_structure_has_name (structure, "audio/ogg")) + { + g_printerr ("validate-sound: Unsupported container format\n"); + return 1; + } + + streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (stream_info)); + + if (streams->next) + { + g_printerr ("validate-sound: Only a single stream is allowed\n"); + return 1; + } + + audio_info = gst_discoverer_stream_info_ref (streams->data); + } + else + { + audio_info = g_steal_pointer (&stream_info); + } + + if (GST_IS_DISCOVERER_AUDIO_INFO (audio_info)) + { + g_autoptr(GstCaps) caps = NULL; + GstStructure *structure = NULL; + + caps = gst_discoverer_stream_info_get_caps (audio_info); + structure = gst_caps_get_structure (caps, 0); + + if (!gst_caps_is_fixed (caps) || gst_caps_get_size (caps) != 1) + { + g_printerr ("validate-sound: Media format is to complex\n"); + return 1; + } + + if (gst_structure_has_name (structure, "audio/x-wav")) + { + format = "wav/pcm"; + } + else if (gst_structure_has_name (gst_caps_get_structure (caps, 0), "audio/x-vorbis")) + { + format = "ogg/vorbis"; + } + else if (gst_structure_has_name (gst_caps_get_structure (caps, 0), "audio/x-opus")) + { + format = "ogg/opus"; + } + } + + if (format == NULL) + { + g_printerr ("validate-sound: Unsupported sound format\n"); + return 1; + } + + key_file = g_key_file_new (); + g_key_file_set_string (key_file, SOUND_VALIDATOR_GROUP, "format", format); + key_file_data = g_key_file_to_data (key_file, NULL, NULL); + g_print ("%s", key_file_data); + + close (input_fd); + + return 0; +} + +#ifdef HELPER + +G_GNUC_NULL_TERMINATED +static void +add_args (GPtrArray *argv_array, ...) +{ + va_list args; + const char *arg; + + va_start (args, argv_array); + while ((arg = va_arg (args, const gchar *))) + g_ptr_array_add (argv_array, g_strdup (arg)); + va_end (args); +} + +static gboolean +path_is_usrmerged (const char *dir) +{ + /* does /dir point to /usr/dir? */ + g_autofree char *target = NULL; + GStatBuf stat_buf_src, stat_buf_target; + + if (g_stat (dir, &stat_buf_src) < 0) + return FALSE; + + target = g_strdup_printf ("/usr/%s", dir); + + if (g_stat (target, &stat_buf_target) < 0) + return FALSE; + + return (stat_buf_src.st_dev == stat_buf_target.st_dev) && + (stat_buf_src.st_ino == stat_buf_target.st_ino); +} + +const char * +flatpak_get_bwrap (void) +{ + const char *e = g_getenv ("FLATPAK_BWRAP"); + + if (e != NULL) + return e; + + return HELPER; +} + +static int +rerun_in_sandbox (int input_fd) +{ + const char * const usrmerged_dirs[] = { "bin", "lib32", "lib64", "lib", "sbin" }; + int i; + g_autoptr(GPtrArray) args = g_ptr_array_new_with_free_func (g_free); + char validate_sound[PATH_MAX + 1]; + ssize_t symlink_size; + g_autofree char* arg_input_fd = NULL; + + symlink_size = readlink ("/proc/self/exe", validate_sound, sizeof (validate_sound) - 1); + if (symlink_size < 0 || (size_t) symlink_size >= sizeof (validate_sound)) + { + g_printerr ("Error: failed to read /proc/self/exe\n"); + return 1; + } + + validate_sound[symlink_size] = 0; + + add_args (args, + flatpak_get_bwrap (), + "--unshare-ipc", + "--unshare-net", + "--unshare-pid", + "--tmpfs", "/tmp", + "--proc", "/proc", + "--dev", "/dev", + "--ro-bind", "/usr", "/usr", + "--ro-bind-try", "/etc/ld.so.cache", "/etc/ld.so.cache", + "--ro-bind", validate_sound, validate_sound, + NULL); + + /* These directories might be symlinks into /usr/... */ + for (i = 0; i < G_N_ELEMENTS (usrmerged_dirs); i++) + { + g_autofree char *absolute_dir = g_strdup_printf ("/%s", usrmerged_dirs[i]); + + if (!g_file_test (absolute_dir, G_FILE_TEST_EXISTS)) + continue; + + if (path_is_usrmerged (absolute_dir)) + { + g_autofree char *symlink_target = g_strdup_printf ("/usr/%s", absolute_dir); + + add_args (args, + "--symlink", symlink_target, absolute_dir, + NULL); + } + else + { + add_args (args, + "--ro-bind", absolute_dir, absolute_dir, + NULL); + } + } + + add_args (args, + "--chdir", "/", + "--setenv", "GIO_USE_VFS", "local", + "--unsetenv", "TMPDIR", + "--die-with-parent", + NULL); + + if (g_getenv ("G_MESSAGES_DEBUG")) + add_args (args, "--setenv", "G_MESSAGES_DEBUG", g_getenv ("G_MESSAGES_DEBUG"), NULL); + if (g_getenv ("G_MESSAGES_PREFIXED")) + add_args (args, "--setenv", "G_MESSAGES_PREFIXED", g_getenv ("G_MESSAGES_PREFIXED"), NULL); + + + arg_input_fd = g_strdup_printf ("%d", input_fd); + add_args (args, validate_sound, "--fd", arg_input_fd, NULL); + g_ptr_array_add (args, NULL); + + execvpe (flatpak_get_bwrap (), (char **) args->pdata, NULL); + /* If we get here, then execvpe() failed. */ + g_printerr ("Sound validation: execvpe %s: %s\n", flatpak_get_bwrap (), g_strerror (errno)); + return 1; +} +#endif + +static gboolean opt_sandbox; +static gchar *opt_path = NULL; +static gint opt_fd = -1; + +static GOptionEntry entries[] = { + { "sandbox", 0, 0, G_OPTION_ARG_NONE, &opt_sandbox, "Run in a sandbox", NULL }, + { "path", 0, 0, G_OPTION_ARG_FILENAME, &opt_path, "Read sound data from given file path", "PATH" }, + { "fd", 0, 0, G_OPTION_ARG_INT, &opt_fd, "Read sound data from given file descriptor", "FD" }, + { NULL } +}; + +int +main (int argc, char *argv[]) +{ + g_autoptr(GOptionContext) context = NULL; + g_autoptr(GError) error = NULL; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("Error: %s\n", error->message); + return 1; + } + + if (opt_path != NULL && opt_fd != -1) + { + g_printerr ("Error: Only --path or --fd can be given\n"); + return 1; + } + + if (opt_path) + { + opt_fd = g_open (opt_path, O_RDONLY, 0); + if (opt_fd == -1) + { + g_printerr ("Error: Couldn't open file\n"); + return 1; + } + } + else if (opt_fd == -1) + { + g_autofree char *help = NULL; + + help = g_option_context_get_help (context, TRUE, NULL); + g_printerr ("Error: Either --path or --fd needs to be given\n\n%s", help); + return 1; + } + +#ifdef HELPER + if (opt_sandbox) + return rerun_in_sandbox (opt_fd); + else +#endif + return validate_sound (opt_fd); +} diff --git a/src/wallpaper.c b/src/wallpaper.c index c4912a0..18de772 100644 --- a/src/wallpaper.c +++ b/src/wallpaper.c @@ -1,10 +1,12 @@ /* * Copyright © 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -27,8 +29,8 @@ #include #include "wallpaper.h" -#include "permissions.h" -#include "request.h" +#include "xdp-permissions.h" +#include "xdp-request.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" @@ -61,19 +63,19 @@ G_DEFINE_TYPE_WITH_CODE (Wallpaper, wallpaper, XDP_DBUS_TYPE_WALLPAPER_SKELETON, wallpaper_iface_init)); static void -send_response (Request *request, +send_response (XdpRequest *request, guint response) { if (request->exported) { - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_debug ("sending response: %d", response); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&opt_builder)); - request_unexport (request); + xdp_request_unexport (request); } } @@ -84,7 +86,7 @@ handle_set_wallpaper_uri_done (GObject *source, { guint response = 2; g_autoptr(GError) error = NULL; - Request *request = data; + XdpRequest *request = data; if (!xdp_dbus_impl_wallpaper_call_set_wallpaper_uri_finish (XDP_DBUS_IMPL_WALLPAPER (source), &response, @@ -123,17 +125,18 @@ handle_set_wallpaper_in_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable) { - Request *request = (Request *)task_data; + XdpRequest *request = XDP_REQUEST (task_data); const char *parent_window; const char *id = xdp_app_info_get_id (request->app_info); g_autoptr(GError) error = NULL; g_autofree char *uri = NULL; - GVariantBuilder opt_builder; + g_auto(GVariantBuilder) opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autoptr(XdpDbusImplRequest) impl_request = NULL; GVariant *options; gboolean show_preview = FALSE; - int fd; - Permission permission; + g_autofd int fd = -1; + XdpPermission permission; REQUEST_AUTOLOCK (request); @@ -142,41 +145,42 @@ handle_set_wallpaper_in_thread_func (GTask *task, fd = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "fd")); options = ((GVariant *)g_object_get_data (G_OBJECT (request), "options")); + g_object_set_data (G_OBJECT (request), "fd", GINT_TO_POINTER (-1)); + if (uri != NULL && fd != -1) { g_warning ("Rejecting invalid set-wallpaper request (both URI and fd are set)"); if (request->exported) { - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), XDG_DESKTOP_PORTAL_RESPONSE_OTHER, g_variant_builder_end (&opt_builder)); - request_unexport (request); + xdp_request_unexport (request); } return; } - permission = get_permission_sync (id, PERMISSION_TABLE, PERMISSION_ID); + permission = xdp_get_permission_sync (id, PERMISSION_TABLE, PERMISSION_ID); - if (permission == PERMISSION_NO) + if (permission == XDP_PERMISSION_NO) { send_response (request, 2); return; } g_variant_lookup (options, "show-preview", "b", &show_preview); - if (!show_preview && permission != PERMISSION_YES) + if (!show_preview && permission != XDP_PERMISSION_YES) { guint access_response = 2; g_autoptr(GVariant) access_results = NULL; - GVariantBuilder access_opt_builder; + g_auto(GVariantBuilder) access_opt_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); g_autofree gchar *app_id = NULL; g_autofree gchar *title = NULL; g_autofree gchar *subtitle = NULL; const gchar *body; - g_variant_builder_init (&access_opt_builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&access_opt_builder, "{sv}", "deny_label", g_variant_new_string (_("Deny"))); g_variant_builder_add (&access_opt_builder, "{sv}", @@ -186,11 +190,9 @@ handle_set_wallpaper_in_thread_func (GTask *task, if (g_strcmp0 (id, "") != 0) { - g_autoptr(GAppInfo) info = NULL; + GAppInfo *info = xdp_app_info_get_gappinfo (request->app_info); const gchar *name = NULL; - info = xdp_app_info_load_app_info (request->app_info); - if (info) { name = g_app_info_get_display_name (G_APP_INFO (info)); @@ -235,8 +237,8 @@ handle_set_wallpaper_in_thread_func (GTask *task, return; } - if (permission == PERMISSION_UNSET) - set_permission_sync (id, PERMISSION_TABLE, PERMISSION_ID, access_response == 0 ? PERMISSION_YES : PERMISSION_NO); + if (permission == XDP_PERMISSION_UNSET) + xdp_set_permission_sync (id, PERMISSION_TABLE, PERMISSION_ID, access_response == 0 ? XDP_PERMISSION_YES : XDP_PERMISSION_NO); if (access_response != 0) { @@ -257,19 +259,16 @@ handle_set_wallpaper_in_thread_func (GTask *task, /* Reject the request */ if (request->exported) { - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), XDG_DESKTOP_PORTAL_RESPONSE_OTHER, g_variant_builder_end (&opt_builder)); - request_unexport (request); + xdp_request_unexport (request); } return; } uri = g_filename_to_uri (path, NULL, NULL); g_object_set_data_full (G_OBJECT (request), "uri", g_strdup (uri), g_free); - close (fd); - g_object_set_data (G_OBJECT (request), "fd", GINT_TO_POINTER (-1)); } impl_request = xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), @@ -285,9 +284,8 @@ handle_set_wallpaper_in_thread_func (GTask *task, return; } - request_set_impl_request (request, impl_request); + xdp_request_set_impl_request (request, impl_request); - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_filter_options (options, &opt_builder, wallpaper_options, G_N_ELEMENTS (wallpaper_options), NULL); @@ -311,7 +309,7 @@ handle_set_wallpaper_uri (XdpDbusWallpaper *object, const char *arg_uri, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GTask) task = NULL; g_debug ("Handle SetWallpaperURI"); @@ -323,7 +321,7 @@ handle_set_wallpaper_uri (XdpDbusWallpaper *object, g_variant_ref (arg_options), (GDestroyNotify)g_variant_unref); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_wallpaper_complete_set_wallpaper_uri (object, invocation, request->id); task = g_task_new (object, NULL, NULL, NULL); @@ -341,7 +339,7 @@ handle_set_wallpaper_file (XdpDbusWallpaper *object, GVariant *arg_fd, GVariant *arg_options) { - Request *request = request_from_invocation (invocation); + XdpRequest *request = xdp_request_from_invocation (invocation); g_autoptr(GTask) task = NULL; int fd_id, fd; g_autoptr(GError) error = NULL; @@ -349,6 +347,15 @@ handle_set_wallpaper_file (XdpDbusWallpaper *object, g_debug ("Handle SetWallpaperFile"); g_variant_get (arg_fd, "h", &fd_id); + if (fd_id >= g_unix_fd_list_get_length (fd_list)) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Bad file descriptor index"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + fd = g_unix_fd_list_get (fd_list, fd_id, &error); if (fd == -1) { @@ -363,7 +370,7 @@ handle_set_wallpaper_file (XdpDbusWallpaper *object, g_variant_ref (arg_options), (GDestroyNotify)g_variant_unref); - request_export (request, g_dbus_method_invocation_get_connection (invocation)); + xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); xdp_dbus_wallpaper_complete_set_wallpaper_file (object, invocation, NULL, request->id); task = g_task_new (object, NULL, NULL, NULL); diff --git a/src/wallpaper.h b/src/wallpaper.h index 19ec2ad..aa9ca1b 100644 --- a/src/wallpaper.h +++ b/src/wallpaper.h @@ -1,10 +1,12 @@ /* * Copyright © 2019 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/src/xdg-desktop-portal.c b/src/xdg-desktop-portal.c index e377a96..addb06e 100644 --- a/src/xdg-desktop-portal.c +++ b/src/xdg-desktop-portal.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -25,18 +27,22 @@ #include #include +#include #include #include "xdp-utils.h" +#include "xdp-call.h" #include "xdp-dbus.h" +#include "xdp-documents.h" #include "xdp-impl-dbus.h" +#include "xdp-method-info.h" +#include "xdp-portal-impl.h" +#include "xdp-session-persistence.h" + #include "account.h" #include "background.h" -#include "call.h" #include "camera.h" #include "clipboard.h" -#include "device.h" -#include "documents.h" #include "dynamic-launcher.h" #include "email.h" #include "file-chooser.h" @@ -49,22 +55,23 @@ #include "network-monitor.h" #include "notification.h" #include "open-uri.h" -#include "permissions.h" -#include "portal-impl.h" +#include "xdp-permissions.h" #include "power-profile-monitor.h" #include "print.h" #include "proxy-resolver.h" #include "realtime.h" +#include "registry.h" #include "remote-desktop.h" -#include "request.h" -#include "restore-token.h" +#include "xdp-request.h" #include "screen-cast.h" #include "screenshot.h" #include "secret.h" #include "settings.h" #include "trash.h" +#include "usb.h" #include "wallpaper.h" +static int global_exit_status = 0; static GMainLoop *loop = NULL; gboolean opt_verbose; @@ -78,6 +85,8 @@ static GOptionEntry entries[] = { { NULL } }; +XdpDbusImplLockdown *lockdown; + static void message_handler (const gchar *log_domain, GLogLevelFlags log_level, @@ -110,63 +119,18 @@ method_needs_request (GDBusMethodInvocation *invocation) { const char *interface; const char *method; + const XdpMethodInfo *method_info; interface = g_dbus_method_invocation_get_interface_name (invocation); method = g_dbus_method_invocation_get_method_name (invocation); - if (strcmp (interface, "org.freedesktop.portal.ScreenCast") == 0) - { - if (strcmp (method, "OpenPipeWireRemote") == 0) - return FALSE; - else - return TRUE; - } - else if (strcmp (interface, "org.freedesktop.portal.RemoteDesktop") == 0) - { - if (strstr (method, "Notify") == method || strcmp (method, "ConnectToEIS") == 0) - return FALSE; - else - return TRUE; - } - else if (strcmp (interface, "org.freedesktop.portal.Clipboard") == 0) - { - return FALSE; - } - else if (strcmp (interface, "org.freedesktop.portal.Camera") == 0) - { - if (strcmp (method, "OpenPipeWireRemote") == 0) - return FALSE; - else - return TRUE; - } - else if (strcmp (interface, "org.freedesktop.portal.DynamicLauncher") == 0) - { - if (strcmp (method, "PrepareInstall") == 0) - return TRUE; - else - return FALSE; - } - else if (strcmp (interface, "org.freedesktop.portal.Background") == 0) - { - if (strcmp (method, "SetStatus") == 0) - return FALSE; - else - return TRUE; - } - else if (strcmp (interface, "org.freedesktop.portal.InputCapture") == 0) - { - if (strcmp (method, "ConnectToEIS") == 0 || - strcmp (method, "Enable") == 0 || - strcmp (method, "Disable") == 0 || - strcmp (method, "Release") == 0) - return FALSE; - else - return TRUE; - } - else - { - return TRUE; - } + method_info = xdp_method_info_find (interface, method); + + if (!method_info) + g_warning ("Support for %s::%s missing in %s", + interface, method, G_STRLOC); + + return method_info ? method_info->uses_request : TRUE; } static gboolean @@ -175,10 +139,9 @@ authorize_callback (GDBusInterfaceSkeleton *interface, gpointer user_data) { g_autoptr(XdpAppInfo) app_info = NULL; - g_autoptr(GError) error = NULL; - app_info = xdp_invocation_lookup_app_info_sync (invocation, NULL, &error); + app_info = xdp_invocation_ensure_app_info_sync (invocation, NULL, &error); if (app_info == NULL) { g_dbus_method_invocation_return_error (invocation, @@ -189,9 +152,9 @@ authorize_callback (GDBusInterfaceSkeleton *interface, } if (method_needs_request (invocation)) - request_init_invocation (invocation, app_info); + xdp_request_init_invocation (invocation, app_info); else - call_init_invocation (invocation, app_info); + xdp_call_init_invocation (invocation, app_info); return TRUE; } @@ -225,6 +188,41 @@ export_portal_implementation (GDBusConnection *connection, g_debug ("providing portal %s", g_dbus_interface_skeleton_get_info (skeleton)->name); } +static void +export_host_portal_implementation (GDBusConnection *connection, + GDBusInterfaceSkeleton *skeleton) +{ + /* Host portal dbus method invocations run in the main thread without yielding + * to the main loop. This means that any later method call of any portal will + * see the effects of the host portal method call. + * + * This is important because the Registry modifies the XdpAppInfo and later + * method calls must see the modified value. + */ + + g_autoptr(GError) error = NULL; + + if (skeleton == NULL) + { + g_warning ("No skeleton to export"); + return; + } + + g_dbus_interface_skeleton_set_flags (skeleton, + G_DBUS_INTERFACE_SKELETON_FLAGS_NONE); + + if (!g_dbus_interface_skeleton_export (skeleton, + connection, + DESKTOP_PORTAL_OBJECT_PATH, + &error)) + { + g_warning ("Error: %s", error->message); + return; + } + + g_debug ("providing portal %s", g_dbus_interface_skeleton_get_info (skeleton)->name); +} + static void peer_died_cb (const char *name) { @@ -233,25 +231,43 @@ peer_died_cb (const char *name) xdp_session_persistence_delete_transient_permissions_for_sender (name); } +static void +exit_with_status (int status) +{ + global_exit_status = status; + g_main_loop_quit (loop); +} + static void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { - PortalImplementation *implementation; - PortalImplementation *lockdown_impl; - PortalImplementation *access_impl; - g_autoptr(GError) error = NULL; - XdpDbusImplLockdown *lockdown; + XdpPortalImplementation *implementation; + XdpPortalImplementation *lockdown_impl; + XdpPortalImplementation *access_impl; GQuark portal_errors G_GNUC_UNUSED; GPtrArray *impls; + g_autoptr(GError) error = NULL; /* make sure errors are registered */ portal_errors = XDG_DESKTOP_PORTAL_ERROR; xdp_connection_track_name_owners (connection, peer_died_cb); - init_document_proxy (connection); - init_permission_store (connection); + + if (!xdp_init_permission_store (connection, &error)) + { + g_critical ("No permission store: %s", error->message); + exit_with_status (1); + return; + } + + if (!xdp_init_document_proxy (connection, &error)) + { + g_critical ("No document portal: %s", error->message); + exit_with_status (1); + return; + } lockdown_impl = find_portal_implementation ("org.freedesktop.impl.portal.Lockdown"); if (lockdown_impl != NULL) @@ -259,8 +275,9 @@ on_bus_acquired (GDBusConnection *connection, G_DBUS_PROXY_FLAGS_NONE, lockdown_impl->dbus_name, DESKTOP_PORTAL_OBJECT_PATH, - NULL, &error); - else + NULL, NULL); + + if (lockdown == NULL) lockdown = xdp_dbus_impl_lockdown_skeleton_new (); export_portal_implementation (connection, memory_monitor_create (connection)); @@ -272,7 +289,8 @@ on_bus_acquired (GDBusConnection *connection, export_portal_implementation (connection, realtime_create (connection)); impls = find_all_portal_implementations ("org.freedesktop.impl.portal.Settings"); - export_portal_implementation (connection, settings_create (connection, impls)); + if (impls->len > 0) + export_portal_implementation (connection, settings_create (connection, impls)); g_ptr_array_free (impls, TRUE); implementation = find_portal_implementation ("org.freedesktop.impl.portal.FileChooser"); @@ -303,12 +321,8 @@ on_bus_acquired (GDBusConnection *connection, access_impl = find_portal_implementation ("org.freedesktop.impl.portal.Access"); if (access_impl != NULL) { - PortalImplementation *tmp; + XdpPortalImplementation *tmp; - export_portal_implementation (connection, - device_create (connection, - access_impl->dbus_name, - lockdown)); #ifdef HAVE_GEOCLUE export_portal_implementation (connection, location_create (connection, @@ -317,7 +331,9 @@ on_bus_acquired (GDBusConnection *connection, #endif export_portal_implementation (connection, - camera_create (connection, lockdown)); + camera_create (connection, + access_impl->dbus_name, + lockdown)); tmp = find_portal_implementation ("org.freedesktop.impl.portal.Screenshot"); if (tmp != NULL) @@ -385,6 +401,15 @@ on_bus_acquired (GDBusConnection *connection, if (implementation != NULL) export_portal_implementation (connection, input_capture_create (connection, implementation->dbus_name)); + +#ifdef HAVE_GUDEV + implementation = find_portal_implementation ("org.freedesktop.impl.portal.Usb"); + if (implementation != NULL) + export_portal_implementation (connection, + xdp_usb_create (connection, implementation->dbus_name)); +#endif + + export_host_portal_implementation (connection, registry_create (connection)); } static void @@ -403,13 +428,37 @@ on_name_lost (GDBusConnection *connection, g_main_loop_quit (loop); } +static gboolean +signal_handler_cb (gpointer user_data) +{ + g_main_loop_quit (loop); + g_debug ("Terminated with signal."); + return G_SOURCE_REMOVE; +} + int main (int argc, char *argv[]) { guint owner_id; g_autoptr(GError) error = NULL; g_autoptr(GDBusConnection) session_bus = NULL; - g_autoptr(GOptionContext) context; + g_autoptr(GSource) signal_handler_source = NULL; + g_autoptr(GOptionContext) context = NULL; + + if (g_getenv ("XDG_DESKTOP_PORTAL_WAIT_FOR_DEBUGGER") != NULL) + { + g_printerr ("\ndesktop portal (PID %d) is waiting for a debugger. " + "Use `gdb -p %d` to connect. \n", + getpid (), getpid ()); + + if (raise (SIGSTOP) == -1) + { + g_printerr ("Failed waiting for debugger\n"); + exit (1); + } + + raise (SIGCONT); + } setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); @@ -434,7 +483,7 @@ main (int argc, char *argv[]) "path /org/freedesktop/portal/desktop.\n" "\n" "Documentation for the available D-Bus interfaces can be found at\n" - "https://flatpak.github.io/xdg-desktop-portal/portal-docs.html\n" + "https://flatpak.github.io/xdg-desktop-portal/docs/\n" "\n" "Please report issues at https://github.com/flatpak/xdg-desktop-portal/issues"); g_option_context_add_main_entries (context, entries, NULL); @@ -466,6 +515,13 @@ main (int argc, char *argv[]) loop = g_main_loop_new (NULL, FALSE); + /* Setup a signal handler so that we can quit cleanly. + * This is useful for triggering asan. + */ + signal_handler_source = g_unix_signal_source_new (SIGHUP); + g_source_set_callback (signal_handler_source, G_SOURCE_FUNC (signal_handler_cb), NULL, NULL); + g_source_attach (signal_handler_source, g_main_loop_get_context (loop)); + session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (session_bus == NULL) { @@ -487,5 +543,5 @@ main (int argc, char *argv[]) g_bus_unown_name (owner_id); g_main_loop_unref (loop); - return 0; + return global_exit_status; } diff --git a/src/xdg-desktop-portal.gresource.xml b/src/xdg-desktop-portal.gresource.xml index aa1c434..dfaf11b 100644 --- a/src/xdg-desktop-portal.gresource.xml +++ b/src/xdg-desktop-portal.gresource.xml @@ -1,6 +1,6 @@ - + - + data/org.freedesktop.portal.FileChooser.xml data/org.freedesktop.portal.OpenURI.xml data/org.freedesktop.portal.Print.xml diff --git a/src/xdg-desktop-portal.service.in b/src/xdg-desktop-portal.service.in index 1f8a455..b279af5 100644 --- a/src/xdg-desktop-portal.service.in +++ b/src/xdg-desktop-portal.service.in @@ -1,6 +1,8 @@ [Unit] Description=Portal service PartOf=graphical-session.target +Requires=dbus.service +After=dbus.service [Service] Type=dbus diff --git a/src/xdp-app-info-flatpak-private.h b/src/xdp-app-info-flatpak-private.h new file mode 100644 index 0000000..c86939a --- /dev/null +++ b/src/xdp-app-info-flatpak-private.h @@ -0,0 +1,41 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include "xdp-app-info-private.h" + +struct _XdpAppInfoFlatpakClass +{ + XdpAppInfoClass parent_class; +}; + +#define XDP_TYPE_APP_INFO_FLATPAK (xdp_app_info_flatpak_get_type()) +G_DECLARE_FINAL_TYPE (XdpAppInfoFlatpak, + xdp_app_info_flatpak, + XDP, APP_INFO_FLATPAK, + XdpAppInfo) + +gboolean xdp_is_flatpak (int pid, + gboolean *is_flatpak, + GError **error); + +XdpAppInfo * xdp_app_info_flatpak_new (int pid, + int *pidfd, + GError **error); diff --git a/src/xdp-app-info-flatpak.c b/src/xdp-app-info-flatpak.c new file mode 100644 index 0000000..6002ae2 --- /dev/null +++ b/src/xdp-app-info-flatpak.c @@ -0,0 +1,833 @@ +/* + * Copyright © 2024 Red Hat, Inc + * Copyright © 2024 GNOME Foundation Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Hubert Figuière + */ + +#include "config.h" + +#include +#include +#ifdef HAVE_SYS_VFS_H +#include +#endif + +#include + +#include "xdp-app-info-flatpak-private.h" +#include "xdp-usb-query.h" + +#define FLATPAK_ENGINE_ID "org.flatpak" + +#define FLATPAK_METADATA_GROUP_APPLICATION "Application" +#define FLATPAK_METADATA_KEY_NAME "name" +#define FLATPAK_METADATA_GROUP_INSTANCE "Instance" +#define FLATPAK_METADATA_KEY_APP_PATH "app-path" +#define FLATPAK_METADATA_KEY_ORIGINAL_APP_PATH "original-app-path" +#define FLATPAK_METADATA_KEY_RUNTIME_PATH "runtime-path" +#define FLATPAK_METADATA_KEY_INSTANCE_ID "instance-id" +#define FLATPAK_METADATA_GROUP_CONTEXT "Context" +#define FLATPAK_METADATA_KEY_SHARED "shared" +#define FLATPAK_METADATA_CONTEXT_SHARED_NETWORK "network" +#define FLATPAK_METADATA_GROUP_RUNTIME "Runtime" + +struct _XdpAppInfoFlatpak +{ + XdpAppInfo parent; + + GKeyFile *flatpak_info; + GPtrArray *queries; +}; + +G_DEFINE_FINAL_TYPE (XdpAppInfoFlatpak, xdp_app_info_flatpak, XDP_TYPE_APP_INFO) + +static gboolean +is_valid_initial_name_character (gint c, gboolean allow_dash) +{ + return + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_') || (allow_dash && c == '-'); +} + +static gboolean +is_valid_name_character (gint c, gboolean allow_dash) +{ + return + is_valid_initial_name_character (c, allow_dash) || + (c >= '0' && c <= '9'); +} + +static const char * +find_last_char (const char *str, gsize len, int c) +{ + const char *p = str + len - 1; + while (p >= str) + { + if (*p == c) + return p; + p--; + } + return NULL; +} + +/** + * flatpak_is_valid_name: + * @string: The string to check + * + * Checks if @string is a valid application name. + * + * App names are composed of 3 or more elements separated by a period + * ('.') character. All elements must contain at least one character. + * + * Each element must only contain the ASCII characters + * "[A-Z][a-z][0-9]_-". Elements may not begin with a digit. + * Additionally "-" is only allowed in the last element. + * + * App names must not begin with a '.' (period) character. + * + * App names must not exceed 255 characters in length. + * + * The above means that any app name is also a valid DBus well known + * bus name, but not all DBus names are valid app names. The difference are: + * 1) DBus name elements may contain '-' in the non-last element. + * 2) DBus names require only two elements + * + * Returns: %TRUE if valid, %FALSE otherwise. + */ +static gboolean +flatpak_is_valid_name (const char *string) +{ + gssize len; + const gchar *s; + const gchar *end; + const gchar *last_dot; + int dot_count; + gboolean last_element; + + g_return_val_if_fail (string != NULL, FALSE); + + len = strlen (string); + if (G_UNLIKELY (len == 0)) + return FALSE; + + if (G_UNLIKELY (len > 255)) + return FALSE; + + end = string + len; + + last_dot = find_last_char (string, len, '.'); + last_element = FALSE; + + s = string; + if (G_UNLIKELY (*s == '.')) + return FALSE; + + if (G_UNLIKELY (!is_valid_initial_name_character (*s, last_element))) + return FALSE; + + s += 1; + dot_count = 0; + while (s != end) + { + if (*s == '.') + { + if (s == last_dot) + last_element = TRUE; + s += 1; + if (G_UNLIKELY (s == end)) + return FALSE; + if (!is_valid_initial_name_character (*s, last_element)) + return FALSE; + dot_count++; + } + else if (G_UNLIKELY (!is_valid_name_character (*s, last_element))) + return FALSE; + s += 1; + } + + if (G_UNLIKELY (dot_count < 2)) + return FALSE; + + return TRUE; +} + +static gboolean +xdp_app_info_flatpak_is_valid_sub_app_id (XdpAppInfo *app_info, + const char *sub_app_id) +{ + const char *app_id = xdp_app_info_get_id (app_info); + + g_assert (app_id); + + if (!g_str_has_prefix (sub_app_id, app_id)) + return FALSE; + + if (sub_app_id[strlen (app_id)] != '.') + return FALSE; + + return flatpak_is_valid_name (sub_app_id); +} + +static char * +xdp_app_info_flatpak_remap_path (XdpAppInfo *app_info, + const char *path) +{ + XdpAppInfoFlatpak *app_info_flatpak = XDP_APP_INFO_FLATPAK (app_info); + const char *app_id = xdp_app_info_get_id (app_info); + g_autofree char *app_path = NULL; + g_autofree char *runtime_path = NULL; + + g_assert (app_info_flatpak); + g_assert (app_id); + + app_path = g_key_file_get_string (app_info_flatpak->flatpak_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_APP_PATH, + NULL); + runtime_path = g_key_file_get_string (app_info_flatpak->flatpak_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_RUNTIME_PATH, + NULL); + + /* For apps we translate /app and /usr to the installed locations. + * Also, we need to rewrite to drop the /newroot prefix added by + * bubblewrap for other files to work. See + * https://github.com/projectatomic/bubblewrap/pull/172 + * for a bit more information on the /newroot issue. + */ + + if (g_str_has_prefix (path, "/newroot/")) + path = path + strlen ("/newroot"); + + if (app_path != NULL && g_str_has_prefix (path, "/app/")) + return g_build_filename (app_path, path + strlen ("/app/"), NULL); + else if (runtime_path != NULL && g_str_has_prefix (path, "/usr/")) + return g_build_filename (runtime_path, path + strlen ("/usr/"), NULL); + else if (g_str_has_prefix (path, "/run/host/usr/")) + return g_build_filename ("/usr", path + strlen ("/run/host/usr/"), NULL); + else if (g_str_has_prefix (path, "/run/host/etc/")) + return g_build_filename ("/etc", path + strlen ("/run/host/etc/"), NULL); + else if (g_str_has_prefix (path, "/run/flatpak/app/")) + return g_build_filename (g_get_user_runtime_dir (), "app", + path + strlen ("/run/flatpak/app/"), NULL); + else if (g_str_has_prefix (path, "/run/flatpak/doc/")) + return g_build_filename (g_get_user_runtime_dir (), "doc", + path + strlen ("/run/flatpak/doc/"), NULL); + else if (g_str_has_prefix (path, "/var/config/")) + return g_build_filename (g_get_home_dir (), ".var", "app", + app_id, "config", + path + strlen ("/var/config/"), NULL); + else if (g_str_has_prefix (path, "/var/data/")) + return g_build_filename (g_get_home_dir (), ".var", "app", + app_id, "data", + path + strlen ("/var/data/"), NULL); + + return g_strdup (path); +} + +static char ** +rewrite_commandline (XdpAppInfoFlatpak *app_info, + const char * const *commandline, + gboolean quote_escape) +{ + const char *app_id = xdp_app_info_get_id (XDP_APP_INFO (app_info)); + g_autoptr(GPtrArray) args = NULL; + + args = g_ptr_array_new_with_free_func (g_free); + + g_ptr_array_add (args, g_strdup ("flatpak")); + g_ptr_array_add (args, g_strdup ("run")); + + if (commandline && commandline[0]) + { + int i; + g_autofree char *quoted_command = NULL; + + quoted_command = xdp_maybe_quote (commandline[0], quote_escape); + + g_ptr_array_add (args, g_strdup_printf ("--command=%s", quoted_command)); + + /* Always quote the app ID if quote_escape is enabled to make + * rewriting the file simpler in case the app is renamed. + */ + if (quote_escape) + g_ptr_array_add (args, g_shell_quote (app_id)); + else + g_ptr_array_add (args, g_strdup (app_id)); + + for (i = 1; commandline[i]; i++) + g_ptr_array_add (args, xdp_maybe_quote (commandline[i], quote_escape)); + } + else if (quote_escape) + { + g_ptr_array_add (args, g_shell_quote (app_id)); + } + else + { + g_ptr_array_add (args, g_strdup (app_id)); + } + + g_ptr_array_add (args, NULL); + return (char **)g_ptr_array_free (g_steal_pointer (&args), FALSE); +} + +static char * +get_tryexec_path (XdpAppInfoFlatpak *app_info) +{ + const char *app_id = xdp_app_info_get_id (XDP_APP_INFO (app_info)); + g_autofree char *original_app_path = NULL; + g_autofree char *app_path = NULL; + char *path; + g_autofree char *app_slash = NULL; + char *app_slash_pointer; + g_autofree char *tryexec_path = NULL; + + original_app_path = g_key_file_get_string (app_info->flatpak_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_ORIGINAL_APP_PATH, + NULL); + app_path = g_key_file_get_string (app_info->flatpak_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_APP_PATH, + NULL); + path = original_app_path ? original_app_path : app_path; + + if (path == NULL || *path == '\0') + return NULL; + + app_slash = g_strconcat ("app/", app_id, NULL); + + app_slash_pointer = strstr (path, app_slash); + if (app_slash_pointer == NULL) + return NULL; + + /* Terminate path after the flatpak installation path such as + * .local/share/flatpak/ */ + *app_slash_pointer = '\0'; + + /* Find the path to the wrapper script exported by Flatpak, which can be + * used in a desktop file's TryExec= + */ + tryexec_path = g_strconcat (path, "exports/bin/", app_id, NULL); + if (access (tryexec_path, X_OK) != 0) + { + g_debug ("Wrapper script unexpectedly not executable or nonexistent: %s", + tryexec_path); + return NULL; + } + + return g_steal_pointer (&tryexec_path); +} + +static gboolean +xdp_app_info_flatpak_validate_autostart (XdpAppInfo *app_info, + GKeyFile *keyfile, + const char * const *autostart_exec, + GCancellable *cancellable, + GError **error) +{ + XdpAppInfoFlatpak *app_info_flatpak = XDP_APP_INFO_FLATPAK (app_info); + const char *app_id = xdp_app_info_get_id (app_info); + g_auto(GStrv) cmdv = NULL; + g_autofree char *cmd = NULL; + + g_assert (app_info_flatpak); + g_assert (app_id); + + cmdv = rewrite_commandline (app_info_flatpak, + autostart_exec, + FALSE /* don't quote escape */); + cmd = g_strjoinv (" ", cmdv); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_EXEC, + cmd); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + "X-Flatpak", + app_id); + + return TRUE; +} + +static const GPtrArray * +xdp_app_info_flaptak_get_usb_queries (XdpAppInfo *app_info) +{ + XdpAppInfoFlatpak *app_info_flatpak = XDP_APP_INFO_FLATPAK (app_info); + + if (!app_info_flatpak->queries) + { + g_autoptr(GPtrArray) usb_queries = NULL; + + usb_queries = g_ptr_array_new_with_free_func ((GDestroyNotify) xdp_usb_query_free); + + g_auto(GStrv) enumerable_devices = NULL; + g_auto(GStrv) hidden_devices = NULL; + + enumerable_devices = g_key_file_get_string_list (app_info_flatpak->flatpak_info, + "USB Devices", + "enumerable-devices", + NULL, NULL); + + for (size_t i = 0; enumerable_devices && enumerable_devices[i] != NULL; i++) + { + g_autoptr(XdpUsbQuery) query = + xdp_usb_query_from_string (XDP_USB_QUERY_TYPE_ENUMERABLE, enumerable_devices[i]); + + if (query) + g_ptr_array_add (usb_queries, g_steal_pointer (&query)); + } + + hidden_devices = g_key_file_get_string_list (app_info_flatpak->flatpak_info, + "USB Devices", + "hidden-devices", + NULL, NULL); + + for (size_t i = 0; hidden_devices && hidden_devices[i] != NULL; i++) + { + g_autoptr(XdpUsbQuery) query = + xdp_usb_query_from_string (XDP_USB_QUERY_TYPE_HIDDEN, hidden_devices[i]); + + if (query) + g_ptr_array_add (usb_queries, g_steal_pointer (&query)); + } + + g_debug ("Found %d enumerable and %d hidden for app %s", + enumerable_devices ? g_strv_length (enumerable_devices) : 0, + hidden_devices ? g_strv_length (hidden_devices) : 0, + xdp_app_info_get_id (app_info)); + app_info_flatpak->queries = g_steal_pointer (&usb_queries); + } + + return app_info_flatpak->queries; +} + +static gboolean +xdp_app_info_flatpak_validate_dynamic_launcher (XdpAppInfo *app_info, + GKeyFile *key_file, + GError **error) +{ + XdpAppInfoFlatpak *app_info_flatpak = XDP_APP_INFO_FLATPAK (app_info); + const char *app_id = xdp_app_info_get_id (app_info); + g_autofree char *exec = NULL; + g_auto(GStrv) exec_strv = NULL; + g_auto(GStrv) prefixed_exec_strv = NULL; + g_autofree char *prefixed_exec = NULL; + g_autofree char *tryexec_path = NULL; + + g_assert (app_info_flatpak); + g_assert (app_id); + + exec = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "Exec", error); + if (exec == NULL) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Desktop entry given to Install() has no Exec line"); + return FALSE; + } + + if (!g_shell_parse_argv (exec, NULL, &exec_strv, error)) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Desktop entry given to Install() has invalid Exec line"); + return FALSE; + } + + /* Don't let the app give itself access to host files */ + if (g_strv_contains ((const char * const *)exec_strv, "--file-forwarding")) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Desktop entry given to Install() must not use --file-forwarding"); + return FALSE; + } + + prefixed_exec_strv = rewrite_commandline (app_info_flatpak, + (const char * const *)exec_strv, + TRUE /* quote escape */); + prefixed_exec = g_strjoinv (" ", prefixed_exec_strv); + g_key_file_set_value (key_file, G_KEY_FILE_DESKTOP_GROUP, "Exec", prefixed_exec); + + tryexec_path = get_tryexec_path (app_info_flatpak); + if (tryexec_path != NULL) + g_key_file_set_value (key_file, G_KEY_FILE_DESKTOP_GROUP, "TryExec", tryexec_path); + + /* Flatpak checks for this key */ + g_key_file_set_value (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-Flatpak", app_id); + /* Flatpak removes this one for security */ + g_key_file_remove_key (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Bugzilla-ExtraInfoScript", NULL); + + return TRUE; +} + +static void +xdp_app_info_flatpak_dispose (GObject *object) +{ + XdpAppInfoFlatpak *app_info = XDP_APP_INFO_FLATPAK (object); + + g_clear_pointer (&app_info->flatpak_info, g_key_file_free); + g_clear_pointer (&app_info->queries, g_ptr_array_unref); + + G_OBJECT_CLASS (xdp_app_info_flatpak_parent_class)->dispose (object); +} + +static void +xdp_app_info_flatpak_class_init (XdpAppInfoFlatpakClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + XdpAppInfoClass *app_info_class = XDP_APP_INFO_CLASS (klass); + + object_class->dispose = xdp_app_info_flatpak_dispose; + + app_info_class->remap_path = + xdp_app_info_flatpak_remap_path; + app_info_class->get_usb_queries = + xdp_app_info_flaptak_get_usb_queries; + app_info_class->validate_autostart = + xdp_app_info_flatpak_validate_autostart; + app_info_class->validate_dynamic_launcher = + xdp_app_info_flatpak_validate_dynamic_launcher; + app_info_class->is_valid_sub_app_id = + xdp_app_info_flatpak_is_valid_sub_app_id; +} + +static void +xdp_app_info_flatpak_init (XdpAppInfoFlatpak *app_info_flatpak) +{ +} + +static int +open_pid_fd (int proc_fd, + pid_t pid, + GError **error) +{ + char buf[20] = {0, }; + int fd; + + snprintf (buf, sizeof(buf), "%u", (guint) pid); + + fd = openat (proc_fd, buf, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + + if (fd == -1) + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Could not to open '/proc/pid/%u': %s", (guint) pid, + g_strerror (errno)); + + return fd; +} + +static pid_t +get_bwrap_child_pid (JsonNode *root, + GError **error) +{ + JsonObject *cpo; + pid_t pid; + + cpo = json_node_get_object (root); + + pid = json_object_get_int_member (cpo, "child-pid"); + if (pid == 0) + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "child-pid missing"); + + return pid; +} + +static JsonNode * +load_bwrap_info (const char *instance, + GError **error) +{ + g_autoptr(JsonParser) parser = NULL; + g_autoptr(JsonNode) root = NULL; + g_autofree char *data = NULL; + gsize len; + g_autofree char *path = NULL; + + g_return_val_if_fail (instance != NULL, 0); + + path = g_build_filename (g_get_user_runtime_dir (), + ".flatpak", + instance, + "bwrapinfo.json", + NULL); + + if (!g_file_get_contents (path, &data, &len, error)) + return 0; + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, data, len, error)) + { + g_prefix_error (error, "Could not parse '%s': ", path); + return 0; + } + + root = json_node_ref (json_parser_get_root (parser)); + if (!root) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not parse '%s': empty file", path); + return 0; + } + + if (!JSON_NODE_HOLDS_OBJECT (root)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not parse '%s': invalid structure", path); + return 0; + } + + return g_steal_pointer (&root); +} + +static int +get_bwrap_pidfd (const char *instance, + GError **error) +{ + g_autoptr(JsonNode) root = NULL; + DIR *proc; + g_autofd int fd = -1; + pid_t pid; + + if (instance == NULL) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not find instance-id in process's /.flatpak-info"); + return -1; + } + + root = load_bwrap_info (instance, error); + if (root == NULL) + return -1; + + pid = get_bwrap_child_pid (root, error); + if (pid == 0) + return -1; + + proc = opendir ("/proc"); + if (proc == NULL) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Could not open '%s': %s", "/proc", g_strerror (errno)); + return -1; + } + + fd = open_pid_fd (dirfd (proc), pid, error); + closedir (proc); + + return g_steal_fd (&fd); +} + +static int +open_flatpak_info (int pid, + GError **error) +{ + g_autofree char *root_path = NULL; + g_autofd int root_fd = -1; + g_autofd int info_fd = -1; + + root_path = g_strdup_printf ("/proc/%u/root", pid); + root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + if (root_fd == -1) + { + if (errno == EACCES) + { + struct statfs buf; + + /* Access to the root dir isn't allowed. This can happen if the root is on a fuse + * filesystem, such as in a toolbox container. We will never have a fuse rootfs + * in the flatpak case, so in that case its safe to ignore this and + * continue to detect other types of apps. + */ + if (statfs (root_path, &buf) == 0 && + buf.f_type == 0x65735546) /* FUSE_SUPER_MAGIC */ + { + g_set_error (error, XDP_APP_INFO_ERROR, XDP_APP_INFO_ERROR_WRONG_APP_KIND, + "Not a flatpak (fuse rootfs)"); + return -1; + } + } + + /* Otherwise, we should be able to open the root dir. Probably the app died and + we're failing due to /proc/$pid not existing. In that case fail instead + of treating this as privileged. */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unable to open %s", root_path); + return -1; + } + + info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (info_fd == -1) + { + if (errno == ENOENT) + { + /* No file => on the host, return success */ + g_set_error (error, XDP_APP_INFO_ERROR, XDP_APP_INFO_ERROR_WRONG_APP_KIND, + "Not a flatpak (no .flatpak-info)"); + return -1; + } + + /* Some weird error => failure */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unable to open application info file"); + return -1; + } + + return g_steal_fd (&info_fd); +} + +gboolean +xdp_is_flatpak (int pid, + gboolean *is_flatpak, + GError **error) +{ + g_autoptr(GError) local_error = NULL; + g_autofd int info_fd = -1; + + info_fd = open_flatpak_info (pid, &local_error); + if (info_fd == -1 && !g_error_matches (local_error, XDP_APP_INFO_ERROR, + XDP_APP_INFO_ERROR_WRONG_APP_KIND)) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + *is_flatpak = info_fd != -1; + return TRUE; +} + +XdpAppInfo * +xdp_app_info_flatpak_new (int pid, + int *pidfd, + GError **error) +{ + g_autoptr (XdpAppInfoFlatpak) app_info_flatpak = NULL; + XdpAppInfoFlags flags = 0; + g_autofd int info_fd = -1; + struct stat stat_buf; + g_autoptr(GError) local_error = NULL; + g_autoptr(GMappedFile) mapped = NULL; + g_autoptr(GKeyFile) metadata = NULL; + const char *group; + g_autofree char *id = NULL; + g_autofree char *instance = NULL; + g_auto(GStrv) shared = NULL; + gboolean has_network; + g_autofd int bwrap_pidfd = -1; + + info_fd = open_flatpak_info (pid, error); + if (info_fd == -1) + return NULL; + + if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) + { + /* Some weird fd => failure */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unable to open application info file"); + return NULL; + } + + mapped = g_mapped_file_new_from_fd (info_fd, FALSE, &local_error); + if (mapped == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't map .flatpak-info file: %s", local_error->message); + return NULL; + } + + metadata = g_key_file_new (); + + if (!g_key_file_load_from_data (metadata, + g_mapped_file_get_contents (mapped), + g_mapped_file_get_length (mapped), + G_KEY_FILE_NONE, &local_error)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't load .flatpak-info file: %s", local_error->message); + return NULL; + } + + group = FLATPAK_METADATA_GROUP_APPLICATION; + if (g_key_file_has_group (metadata, FLATPAK_METADATA_GROUP_RUNTIME)) + group = FLATPAK_METADATA_GROUP_RUNTIME; + + id = g_key_file_get_string (metadata, + group, + FLATPAK_METADATA_KEY_NAME, + error); + if (id == NULL || !xdp_is_valid_app_id (id)) + return NULL; + + instance = g_key_file_get_string (metadata, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_INSTANCE_ID, + error); + if (instance == NULL) + return NULL; + + shared = g_key_file_get_string_list (metadata, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SHARED, + NULL, NULL); + if (shared) + { + has_network = g_strv_contains ((const char * const *)shared, + FLATPAK_METADATA_CONTEXT_SHARED_NETWORK); + } + else + { + has_network = FALSE; + } + + /* flatpak has a xdg-dbus-proxy running which means we can't get the pidfd + * of the connected process but we can get the pidfd of the bwrap instance + * instead. This is okay because it has the same namespaces as the calling + * process. + */ + bwrap_pidfd = get_bwrap_pidfd (instance, error); + if (bwrap_pidfd == -1) + return NULL; + + /* TODO: we can use pidfd to make sure we didn't race for sure */ + + flags |= XDP_APP_INFO_FLAG_SUPPORTS_OPATH; + if (has_network) + flags |= XDP_APP_INFO_FLAG_HAS_NETWORK; + + app_info_flatpak = g_initable_new (XDP_TYPE_APP_INFO_FLATPAK, + NULL, + error, + "engine", FLATPAK_ENGINE_ID, + "flags", flags, + "id", id, + "instance", instance, + "pidfd", g_steal_fd (&bwrap_pidfd), + NULL); + if (!app_info_flatpak) + return NULL; + + app_info_flatpak->flatpak_info = g_steal_pointer (&metadata); + + return XDP_APP_INFO (g_steal_pointer (&app_info_flatpak)); +} diff --git a/src/xdp-app-info-host-private.h b/src/xdp-app-info-host-private.h new file mode 100644 index 0000000..58c2883 --- /dev/null +++ b/src/xdp-app-info-host-private.h @@ -0,0 +1,46 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include "xdp-app-info-private.h" + +struct _XdpAppInfoHostClass +{ + XdpAppInfoClass parent_class; +}; + +#define XDP_TYPE_APP_INFO_HOST (xdp_app_info_host_get_type()) +G_DECLARE_FINAL_TYPE (XdpAppInfoHost, + xdp_app_info_host, + XDP, APP_INFO_HOST, + XdpAppInfo) + +XdpAppInfo * xdp_app_info_host_new (int pid, + int *pidfd); + +XdpAppInfo * +xdp_app_info_host_new_registered (int *pidfd, + const char *app_id, + GError **error); + +#ifdef HAVE_LIBSYSTEMD +XDP_EXPORT_TEST +char * _xdp_app_info_host_parse_app_id_from_unit_name (const char *unit); +#endif diff --git a/src/xdp-app-info-host.c b/src/xdp-app-info-host.c new file mode 100644 index 0000000..39005b2 --- /dev/null +++ b/src/xdp-app-info-host.c @@ -0,0 +1,229 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#ifdef HAVE_LIBSYSTEMD +#include +#include "sd-escape.h" +#endif + +#include "xdp-app-info-host-private.h" +#include "xdp-usb-query.h" + +struct _XdpAppInfoHost +{ + XdpAppInfo parent; + + GPtrArray *usb_queries; +}; + +G_DEFINE_FINAL_TYPE (XdpAppInfoHost, xdp_app_info_host, XDP_TYPE_APP_INFO) + +static const GPtrArray * +xdp_app_info_host_get_usb_queries (XdpAppInfo *app_info) +{ + XdpAppInfoHost *app_info_host = XDP_APP_INFO_HOST (app_info); + + return app_info_host->usb_queries; +} + +static gboolean +xdp_app_info_host_is_valid_sub_app_id (XdpAppInfo *app_info, + const char *sub_app_id) +{ + return TRUE; +} + +static gboolean +xdp_app_info_host_validate_autostart (XdpAppInfo *app_info, + GKeyFile *keyfile, + const char * const *autostart_exec, + GCancellable *cancellable, + GError **error) +{ + return TRUE; +} + +static gboolean +xdp_app_info_host_validate_dynamic_launcher (XdpAppInfo *app_info, + GKeyFile *key_file, + GError **error) +{ + return TRUE; +} + +static void +xdp_app_info_host_dispose (GObject *object) +{ + XdpAppInfoHost *app_info = XDP_APP_INFO_HOST (object); + + g_clear_pointer (&app_info->usb_queries, g_ptr_array_unref); + + G_OBJECT_CLASS (xdp_app_info_host_parent_class)->dispose (object); +} + +static void +xdp_app_info_host_class_init (XdpAppInfoHostClass *klass) +{ + XdpAppInfoClass *app_info_class = XDP_APP_INFO_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = xdp_app_info_host_dispose; + + app_info_class->get_usb_queries = + xdp_app_info_host_get_usb_queries; + app_info_class->validate_autostart = + xdp_app_info_host_validate_autostart; + app_info_class->validate_dynamic_launcher = + xdp_app_info_host_validate_dynamic_launcher; + app_info_class->is_valid_sub_app_id = + xdp_app_info_host_is_valid_sub_app_id; +} + +static void +xdp_app_info_host_init (XdpAppInfoHost *app_info_host) +{ + g_autoptr(XdpUsbQuery) query = NULL; + + app_info_host->usb_queries = + g_ptr_array_new_with_free_func ((GDestroyNotify) xdp_usb_query_free); + + query = xdp_usb_query_from_string (XDP_USB_QUERY_TYPE_ENUMERABLE, "all"); + if (query) + g_ptr_array_add (app_info_host->usb_queries, g_steal_pointer (&query)); +} + +#ifdef HAVE_LIBSYSTEMD +char * +_xdp_app_info_host_parse_app_id_from_unit_name (const char *unit) +{ + g_autoptr(GRegex) regex1 = NULL; + g_autoptr(GRegex) regex2 = NULL; + g_autoptr(GMatchInfo) match = NULL; + g_autoptr(GError) error = NULL; + g_autofree char *app_id = NULL; + + g_assert (g_str_has_prefix (unit, "app-")); + + /* + * From https://systemd.io/DESKTOP_ENVIRONMENTS/ the format is one of: + * app[-]--.scope + * app[-]--.slice + */ + regex1 = g_regex_new ("^app-(?:[[:alnum:]]+\\-)?(.+?)(?:\\-[[:alnum:]]*)(?:\\.scope|\\.slice)$", 0, 0, &error); + g_assert (error == NULL); + /* + * app[-]--autostart.service -> no longer true since systemd v248 + * app[-]-[@].service + */ + regex2 = g_regex_new ("^app-(?:[[:alnum:]]+\\-)?(.+?)(?:@[[:alnum:]]*|\\-autostart)?\\.service$", 0, 0, &error); + g_assert (error == NULL); + + if (!g_regex_match (regex1, unit, 0, &match)) + g_clear_pointer (&match, g_match_info_unref); + + if (match == NULL && !g_regex_match (regex2, unit, 0, &match)) + g_clear_pointer (&match, g_match_info_unref); + + if (match != NULL) + { + g_autofree char *escaped_app_id = NULL; + /* Unescape the unit name which may have \x hex codes in it, e.g. + * "app-gnome-org.gnome.Evolution\x2dalarm\x2dnotify-2437.scope" + */ + escaped_app_id = g_match_info_fetch (match, 1); + if (cunescape (escaped_app_id, UNESCAPE_RELAX, &app_id) < 0) + app_id = g_strdup (""); + } + else + { + app_id = g_strdup (""); + } + + return g_steal_pointer (&app_id); +} +#endif /* HAVE_LIBSYSTEMD */ + +static char * +get_appid_from_pid (pid_t pid) +{ +#ifdef HAVE_LIBSYSTEMD + g_autofree char *unit = NULL; + int res; + + res = sd_pid_get_user_unit (pid, &unit); + /* + * The session might not be managed by systemd or there could be an error + * fetching our own systemd units or the unit might not be started by the + * desktop environment (e.g. it's a script run from terminal). + */ + if (res == -ENODATA || res < 0 || !unit || !g_str_has_prefix (unit, "app-")) + return g_strdup (""); + + return _xdp_app_info_host_parse_app_id_from_unit_name (unit); + +#else + /* FIXME: we should return NULL and handle id==NULL at callers */ + return g_strdup (""); +#endif /* HAVE_LIBSYSTEMD */ +} + +XdpAppInfo * +xdp_app_info_host_new_registered (int *pidfd, + const char *app_id, + GError **error) +{ + g_autoptr(XdpAppInfoHost) app_info_host = NULL; + + app_info_host = g_initable_new (XDP_TYPE_APP_INFO_HOST, + NULL, + error, + "engine", NULL, + "id", app_id, + "pidfd", g_steal_fd (pidfd), + "flags", XDP_APP_INFO_FLAG_HAS_NETWORK | + XDP_APP_INFO_FLAG_SUPPORTS_OPATH | + XDP_APP_INFO_FLAG_REQUIRE_GAPPINFO, + NULL); + + return XDP_APP_INFO (g_steal_pointer (&app_info_host)); +} + +XdpAppInfo * +xdp_app_info_host_new (int pid, + int *pidfd) +{ + g_autoptr(XdpAppInfoHost) app_info_host = NULL; + g_autofree char *app_id = NULL; + + app_id = get_appid_from_pid (pid); + + app_info_host = g_initable_new (XDP_TYPE_APP_INFO_HOST, + NULL, + NULL, + "engine", NULL, + "id", app_id, + "pidfd", g_steal_fd (pidfd), + "flags", XDP_APP_INFO_FLAG_HAS_NETWORK | + XDP_APP_INFO_FLAG_SUPPORTS_OPATH, + NULL); + + return XDP_APP_INFO (g_steal_pointer (&app_info_host)); +} diff --git a/src/xdp-app-info-private.h b/src/xdp-app-info-private.h new file mode 100644 index 0000000..f89c8fe --- /dev/null +++ b/src/xdp-app-info-private.h @@ -0,0 +1,57 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include "xdp-utils.h" + +#include "xdp-app-info.h" + +typedef enum _XdpAppInfoFlags +{ + XDP_APP_INFO_FLAG_HAS_NETWORK = (1 << 0), + XDP_APP_INFO_FLAG_SUPPORTS_OPATH = (1 << 1), + XDP_APP_INFO_FLAG_REQUIRE_GAPPINFO = (1 << 2), +} XdpAppInfoFlags; + +struct _XdpAppInfoClass +{ + GObjectClass parent_class; + + gboolean (*is_valid_sub_app_id) (XdpAppInfo *app_info, + const char *sub_app_id); + + char * (*remap_path) (XdpAppInfo *app_info, + const char *path); + + const GPtrArray * (*get_usb_queries) (XdpAppInfo *app_info); + + gboolean (*validate_autostart) (XdpAppInfo *app_info, + GKeyFile *keyfile, + const char * const *autostart_exec, + GCancellable *cancellable, + GError **error); + + gboolean (*validate_dynamic_launcher) (XdpAppInfo *app_info, + GKeyFile *key_file, + GError **error); + + GAppInfo * (*create_gappinfo) (XdpAppInfo *app_info); +}; + diff --git a/src/xdp-app-info-snap-private.h b/src/xdp-app-info-snap-private.h new file mode 100644 index 0000000..93f99cd --- /dev/null +++ b/src/xdp-app-info-snap-private.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include "xdp-app-info-private.h" + +struct _XdpAppInfoSnapClass +{ + XdpAppInfoClass parent_class; +}; + +#define XDP_TYPE_APP_INFO_SNAP (xdp_app_info_snap_get_type()) +G_DECLARE_FINAL_TYPE (XdpAppInfoSnap, + xdp_app_info_snap, + XDP, APP_INFO_SNAP, + XdpAppInfo) + +gboolean xdp_is_snap (int pid, + gboolean *is_snap, + GError **error); + +XdpAppInfo * xdp_app_info_snap_new (int pid, + int *pidfd, + GError **error); + +XDP_EXPORT_TEST +int _xdp_app_info_snap_parse_cgroup_file (FILE *f, + gboolean *is_snap); diff --git a/src/xdp-app-info-snap.c b/src/xdp-app-info-snap.c new file mode 100644 index 0000000..5cea727 --- /dev/null +++ b/src/xdp-app-info-snap.c @@ -0,0 +1,326 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include +#include +#ifdef HAVE_SYS_VFS_H +#include +#endif + +#include "xdp-app-info-snap-private.h" + +#define SNAP_METADATA_GROUP_INFO "Snap Info" +#define SNAP_METADATA_KEY_INSTANCE_NAME "InstanceName" +#define SNAP_METADATA_KEY_DESKTOP_FILE "DesktopFile" +#define SNAP_METADATA_KEY_NETWORK "HasNetworkStatus" + +struct _XdpAppInfoSnap +{ + XdpAppInfo parent; + + char *desktop_file; +}; + +G_DEFINE_FINAL_TYPE (XdpAppInfoSnap, xdp_app_info_snap, XDP_TYPE_APP_INFO) + +enum +{ + PROP_0, + PROP_DESKTOP_FILE, + N_PROPS, +}; + +static GParamSpec *properties [N_PROPS]; + +static GAppInfo * +xdp_app_info_snap_create_gappinfo (XdpAppInfo *app_info) +{ + XdpAppInfoSnap *app_info_snap = XDP_APP_INFO_SNAP (app_info); + g_autoptr(GAppInfo) gappinfo = NULL; + + gappinfo = + G_APP_INFO (g_desktop_app_info_new (app_info_snap->desktop_file)); + + return g_steal_pointer (&gappinfo); +} + +static void +xdp_app_info_snap_finalize (GObject *object) +{ + XdpAppInfoSnap *app_info_snap = XDP_APP_INFO_SNAP (object); + + g_clear_pointer (&app_info_snap->desktop_file, g_free); + + G_OBJECT_CLASS (xdp_app_info_snap_parent_class)->finalize (object); +} + +static void +xdp_app_info_snap_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + XdpAppInfoSnap *app_info_snap = XDP_APP_INFO_SNAP (object); + + switch (prop_id) + { + case PROP_DESKTOP_FILE: + g_value_set_string (value, app_info_snap->desktop_file); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +xdp_app_info_snap_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + XdpAppInfoSnap *app_info_snap = XDP_APP_INFO_SNAP (object); + + switch (prop_id) + { + case PROP_DESKTOP_FILE: + g_assert (app_info_snap->desktop_file == NULL); + app_info_snap->desktop_file = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +xdp_app_info_snap_class_init (XdpAppInfoSnapClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + XdpAppInfoClass *app_info_class = XDP_APP_INFO_CLASS (klass); + + object_class->finalize = xdp_app_info_snap_finalize; + object_class->get_property = xdp_app_info_snap_get_property; + object_class->set_property = xdp_app_info_snap_set_property; + + app_info_class->create_gappinfo = xdp_app_info_snap_create_gappinfo; + + properties[PROP_DESKTOP_FILE] = + g_param_spec_string ("desktop-file", NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +xdp_app_info_snap_init (XdpAppInfoSnap *app_info_snap) +{ +} + +int +_xdp_app_info_snap_parse_cgroup_file (FILE *f, + gboolean *is_snap) +{ + ssize_t n; + g_autofree char *id = NULL; + g_autofree char *controller = NULL; + g_autofree char *cgroup = NULL; + size_t id_len = 0, controller_len = 0, cgroup_len = 0; + + g_return_val_if_fail(f != NULL, -1); + g_return_val_if_fail(is_snap != NULL, -1); + + *is_snap = FALSE; + do + { + n = getdelim (&id, &id_len, ':', f); + if (n == -1) break; + n = getdelim (&controller, &controller_len, ':', f); + if (n == -1) break; + n = getdelim (&cgroup, &cgroup_len, '\n', f); + if (n == -1) break; + + /* Only consider the freezer, systemd group or unified cgroup + * hierarchies */ + if ((strcmp (controller, "freezer:") == 0 || + strcmp (controller, "name=systemd:") == 0 || + strcmp (controller, ":") == 0) && + strstr (cgroup, "/snap.") != NULL) + { + *is_snap = TRUE; + break; + } + } + while (n >= 0); + + if (n < 0 && !feof(f)) return -1; + + return 0; +} + +static gboolean +pid_is_snap (pid_t pid, + GError **error) +{ + g_autofree char *cgroup_path = NULL;; + int fd; + FILE *f = NULL; + gboolean is_snap = FALSE; + int err = 0; + + g_return_val_if_fail(pid > 0, FALSE); + + cgroup_path = g_strdup_printf ("/proc/%u/cgroup", (guint) pid); + fd = open (cgroup_path, O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd == -1) + { + err = errno; + goto end; + } + + f = fdopen (fd, "r"); + if (f == NULL) + { + err = errno; + goto end; + } + + fd = -1; /* fd is now owned by f */ + + if (_xdp_app_info_snap_parse_cgroup_file (f, &is_snap) == -1) + err = errno; + + fclose (f); + +end: + /* Silence ENOENT, treating it as "not a snap" */ + if (err != 0 && err != ENOENT) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (err), + "Could not parse cgroup info for pid %u: %s", (guint) pid, + g_strerror (err)); + } + return is_snap; +} + +gboolean +xdp_is_snap (int pid, + gboolean *is_snap, + GError **error) +{ + g_autoptr(GError) local_error = NULL; + + if (!pid_is_snap (pid, &local_error)) + { + if (local_error && !g_error_matches (local_error, XDP_APP_INFO_ERROR, + XDP_APP_INFO_ERROR_WRONG_APP_KIND)) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + *is_snap = FALSE; + return TRUE; + } + else + { + *is_snap = TRUE; + return TRUE; + } +} + +XdpAppInfo * +xdp_app_info_snap_new (int pid, + int *pidfd, + GError **error) +{ + g_autoptr (XdpAppInfoSnap) app_info_snap = NULL; + + g_autoptr(GError) local_error = NULL; + g_autofree char *pid_str = NULL; + g_autofree char *output = NULL; + g_autoptr(GKeyFile) metadata = NULL; + g_autofree char *snap_name = NULL; + g_autofree char *snap_id = NULL; + g_autofree char *desktop_id = NULL; + XdpAppInfoFlags flags = 0; + gboolean has_network; + + /* Check the process's cgroup membership to fail quickly for non-snaps */ + if (!pid_is_snap (pid, error)) + { + g_set_error (error, XDP_APP_INFO_ERROR, XDP_APP_INFO_ERROR_WRONG_APP_KIND, + "Not a snap (cgroup doesn't contain a snap id)"); + return NULL; + } + + pid_str = g_strdup_printf ("%u", (guint) pid); + output = xdp_spawn (error, "snap", "routine", "portal-info", pid_str, NULL); + if (output == NULL) + return NULL; + + metadata = g_key_file_new (); + if (!g_key_file_load_from_data (metadata, output, -1, G_KEY_FILE_NONE, &local_error)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't read snap info for pid %u: %s", pid, local_error->message); + return FALSE; + } + + snap_name = g_key_file_get_string (metadata, + SNAP_METADATA_GROUP_INFO, + SNAP_METADATA_KEY_INSTANCE_NAME, + error); + if (snap_name == NULL) + return NULL; + + snap_id = g_strconcat ("snap.", snap_name, NULL); + + desktop_id = g_key_file_get_string (metadata, + SNAP_METADATA_GROUP_INFO, + SNAP_METADATA_KEY_DESKTOP_FILE, + error); + if (desktop_id == NULL) + return NULL; + + has_network = g_key_file_get_boolean (metadata, + SNAP_METADATA_GROUP_INFO, + SNAP_METADATA_KEY_NETWORK, + NULL); + + if (has_network) + flags |= XDP_APP_INFO_FLAG_HAS_NETWORK; + + app_info_snap = g_initable_new (XDP_TYPE_APP_INFO_SNAP, + NULL, + error, + "engine", "io.snapcraft", + "id", snap_id, + "pidfd", g_steal_fd (pidfd), + "flags", flags, + "desktop-file", desktop_id, + NULL); + + return XDP_APP_INFO (g_steal_pointer (&app_info_snap)); +} diff --git a/src/xdp-app-info-test-private.h b/src/xdp-app-info-test-private.h new file mode 100644 index 0000000..d323323 --- /dev/null +++ b/src/xdp-app-info-test-private.h @@ -0,0 +1,36 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include "xdp-app-info-private.h" + +struct _XdpAppInfoTestClass +{ + XdpAppInfoClass parent_class; +}; + +#define XDP_TYPE_APP_INFO_TEST (xdp_app_info_test_get_type()) +G_DECLARE_FINAL_TYPE (XdpAppInfoTest, + xdp_app_info_test, + XDP, APP_INFO_TEST, + XdpAppInfo) + +XdpAppInfo * xdp_app_info_test_new (const char *app_id, + const char *usb_queries_str); diff --git a/src/xdp-app-info-test.c b/src/xdp-app-info-test.c new file mode 100644 index 0000000..5212f40 --- /dev/null +++ b/src/xdp-app-info-test.c @@ -0,0 +1,149 @@ +/* + * Copyright © 2024 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "xdp-app-info-test-private.h" + +#include "xdp-usb-query.h" + +struct _XdpAppInfoTest +{ + XdpAppInfo parent; + + GPtrArray *usb_queries; +}; + +G_DEFINE_FINAL_TYPE (XdpAppInfoTest, xdp_app_info_test, XDP_TYPE_APP_INFO) + +static gboolean +xdp_app_info_test_validate_autostart (XdpAppInfo *app_info, + GKeyFile *keyfile, + const char * const *autostart_exec, + GCancellable *cancellable, + GError **error) +{ + return TRUE; +} + +gboolean +xdp_app_info_test_validate_dynamic_launcher (XdpAppInfo *app_info, + GKeyFile *key_file, + GError **error) +{ + return TRUE; +} + +static const GPtrArray * +xdp_app_info_test_get_usb_queries (XdpAppInfo *app_info) +{ + XdpAppInfoTest *app_info_test = XDP_APP_INFO_TEST (app_info); + + return app_info_test->usb_queries; +} + +static gboolean +xdp_app_info_test_is_valid_sub_app_id (XdpAppInfo *app_info, + const char *sub_app_id) +{ + return TRUE; +} + +static void +xdp_app_info_test_dispose (GObject *object) +{ + XdpAppInfoTest *app_info = XDP_APP_INFO_TEST (object); + + g_clear_pointer (&app_info->usb_queries, g_ptr_array_unref); + + G_OBJECT_CLASS (xdp_app_info_test_parent_class)->dispose (object); +} + +static void +xdp_app_info_test_class_init (XdpAppInfoTestClass *klass) +{ + XdpAppInfoClass *app_info_class = XDP_APP_INFO_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = xdp_app_info_test_dispose; + + app_info_class->validate_autostart = + xdp_app_info_test_validate_autostart; + app_info_class->validate_dynamic_launcher = + xdp_app_info_test_validate_dynamic_launcher; + app_info_class->get_usb_queries = + xdp_app_info_test_get_usb_queries; + app_info_class->is_valid_sub_app_id = + xdp_app_info_test_is_valid_sub_app_id; +} + +static void +xdp_app_info_test_init (XdpAppInfoTest *app_info_test) +{ +} + +static GPtrArray * +parse_usb_queries_string (const char *usb_queries_str) +{ + g_autoptr(GPtrArray) usb_queries = NULL; + g_auto(GStrv) queries_strs = NULL; + + if (!usb_queries_str) + return NULL; + + usb_queries = + g_ptr_array_new_with_free_func ((GDestroyNotify) xdp_usb_query_free); + + queries_strs = g_strsplit (usb_queries_str, ";", 0); + for (size_t i = 0; queries_strs[i] != NULL; i++) + { + g_autoptr(XdpUsbQuery) query = + xdp_usb_query_from_string (XDP_USB_QUERY_TYPE_ENUMERABLE, + queries_strs[i]); + + if (query) + g_ptr_array_add (usb_queries, g_steal_pointer (&query)); + } + + if (usb_queries->len == 0) + return NULL; + + return g_steal_pointer (&usb_queries); +} + +XdpAppInfo * +xdp_app_info_test_new (const char *app_id, + const char *usb_queries_str) +{ + g_autoptr (XdpAppInfoTest) app_info_test = NULL; + + app_info_test = g_initable_new (XDP_TYPE_APP_INFO_TEST, + NULL, + NULL, + "engine", NULL, + "id", app_id, + "flags", XDP_APP_INFO_FLAG_HAS_NETWORK | + XDP_APP_INFO_FLAG_SUPPORTS_OPATH, + NULL); + g_assert (app_info_test); + + app_info_test->usb_queries = parse_usb_queries_string (usb_queries_str); + + return XDP_APP_INFO (g_steal_pointer (&app_info_test)); +} diff --git a/src/xdp-app-info.c b/src/xdp-app-info.c new file mode 100644 index 0000000..5f29885 --- /dev/null +++ b/src/xdp-app-info.c @@ -0,0 +1,1111 @@ +/* + * Copyright © 2024 Red Hat, Inc + * Copyright © 2024 GNOME Foundation Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Hubert Figuière + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_VFS_H +#include +#endif +#ifdef HAVE_SYS_MOUNT_H +#include +#endif +#ifdef HAVE_LIBSYSTEMD +#include +#include "sd-escape.h" +#endif + +#include +#include + +#include "xdp-app-info-private.h" +#include "xdp-app-info-flatpak-private.h" +#include "xdp-app-info-snap-private.h" +#include "xdp-app-info-host-private.h" +#include "xdp-app-info-test-private.h" +#include "xdp-enum-types.h" +#include "xdp-utils.h" + +#define DBUS_NAME_DBUS "org.freedesktop.DBus" +#define DBUS_INTERFACE_DBUS DBUS_NAME_DBUS +#define DBUS_PATH_DBUS "/org/freedesktop/DBus" + +G_LOCK_DEFINE (app_infos); +static GHashTable *app_info_by_unique_name; + +G_DEFINE_QUARK (XdpAppInfo, xdp_app_info_error); + +typedef struct _XdpAppInfoPrivate +{ + char *engine; + char *id; + char *instance; + int pidfd; + GAppInfo *gappinfo; + XdpAppInfoFlags flags; + + /* pid namespace mapping */ + GMutex pidns_lock; + ino_t pidns_id; +} XdpAppInfoPrivate; + +static void g_initable_init_iface (GInitableIface *iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (XdpAppInfo, xdp_app_info, G_TYPE_OBJECT, + G_ADD_PRIVATE (XdpAppInfo) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, g_initable_init_iface)) + +enum +{ + PROP_0, + PROP_ENGINE, + PROP_FLAGS, + PROP_G_APP_INFO, + PROP_ID, + PROP_INSTANCE, + PROP_PIDFD, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static gboolean +xdp_app_info_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + XdpAppInfo *app_info = XDP_APP_INFO (initable); + XdpAppInfoPrivate *priv = xdp_app_info_get_instance_private (app_info); + + priv->gappinfo = + XDP_APP_INFO_GET_CLASS (app_info)->create_gappinfo (app_info); + + if ((priv->flags & XDP_APP_INFO_FLAG_REQUIRE_GAPPINFO) && !priv->gappinfo) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "App info not found for '%s'", priv->id); + return FALSE; + } + + return TRUE; +} + +static void +g_initable_init_iface (GInitableIface *iface) +{ + iface->init = xdp_app_info_initable_init; +} + +static GAppInfo * +xdp_app_info_real_create_gappinfo (XdpAppInfo *app_info) +{ + XdpAppInfoPrivate *priv = xdp_app_info_get_instance_private (app_info); + g_autoptr(GAppInfo) gappinfo = NULL; + g_autofree char *desktop_id = NULL; + + desktop_id = g_strconcat (priv->id, ".desktop", NULL); + gappinfo = G_APP_INFO (g_desktop_app_info_new (desktop_id)); + + return g_steal_pointer (&gappinfo); +} + +static void +xdp_app_info_dispose (GObject *object) +{ + XdpAppInfoPrivate *priv = + xdp_app_info_get_instance_private (XDP_APP_INFO (object)); + g_autoptr(GError) error = NULL; + + g_clear_pointer (&priv->engine, g_free); + g_clear_pointer (&priv->id, g_free); + g_clear_pointer (&priv->instance, g_free); + g_clear_object (&priv->gappinfo); + + if (!g_clear_fd (&priv->pidfd, &error)) + g_warning ("Error closing pidfd: %s", error->message); + + G_OBJECT_CLASS (xdp_app_info_parent_class)->dispose (object); +} + +static void +xdp_app_info_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + XdpAppInfoPrivate *priv = + xdp_app_info_get_instance_private (XDP_APP_INFO (object)); + + switch (prop_id) + { + case PROP_ENGINE: + g_value_set_string (value, priv->engine); + break; + + case PROP_FLAGS: + g_value_set_flags (value, priv->flags); + break; + + case PROP_G_APP_INFO: + g_value_set_object (value, priv->gappinfo); + break; + + case PROP_ID: + g_value_set_string (value, priv->id); + break; + + case PROP_INSTANCE: + g_value_set_string (value, priv->instance); + break; + + case PROP_PIDFD: + g_value_set_int (value, priv->pidfd); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +xdp_app_info_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + XdpAppInfoPrivate *priv = + xdp_app_info_get_instance_private (XDP_APP_INFO (object)); + + switch (prop_id) + { + case PROP_ENGINE: + g_assert (priv->engine == NULL); + priv->engine = g_value_dup_string (value); + break; + + case PROP_FLAGS: + g_assert (priv->flags == 0); + priv->flags = g_value_get_flags (value); + break; + + case PROP_ID: + g_assert (priv->id == NULL); + priv->id = g_value_dup_string (value); + break; + + case PROP_INSTANCE: + g_assert (priv->instance == NULL); + priv->instance = g_value_dup_string (value); + break; + + case PROP_PIDFD: + g_assert (priv->pidfd == -1); + priv->pidfd = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +xdp_app_info_class_init (XdpAppInfoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = xdp_app_info_dispose; + object_class->get_property = xdp_app_info_get_property; + object_class->set_property = xdp_app_info_set_property; + + klass->create_gappinfo = xdp_app_info_real_create_gappinfo; + + properties[PROP_ENGINE] = + g_param_spec_string ("engine", NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_FLAGS] = + g_param_spec_flags ("flags", NULL, NULL, + XDP_TYPE_APP_INFO_FLAGS, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_G_APP_INFO] = + g_param_spec_object ("g-app-info", NULL, NULL, + G_TYPE_APP_INFO, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_ID] = + g_param_spec_string ("id", NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_INSTANCE] = + g_param_spec_string ("instance", NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_PIDFD] = + g_param_spec_int ("pidfd", NULL, NULL, + -1, G_MAXINT, -1, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +xdp_app_info_init (XdpAppInfo *app_info) +{ + XdpAppInfoPrivate *priv = xdp_app_info_get_instance_private (app_info); + + priv->pidfd = -1; +} + +gboolean +xdp_app_info_is_host (XdpAppInfo *app_info) +{ + XdpAppInfoPrivate *priv; + + g_return_val_if_fail (app_info != NULL, FALSE); + + priv = xdp_app_info_get_instance_private (app_info); + + return priv->engine == NULL; +} + +const char * +xdp_app_info_get_id (XdpAppInfo *app_info) +{ + XdpAppInfoPrivate *priv; + + g_return_val_if_fail (app_info != NULL, NULL); + + priv = xdp_app_info_get_instance_private (app_info); + + return priv->id; +} + +const char * +xdp_app_info_get_instance (XdpAppInfo *app_info) +{ + XdpAppInfoPrivate *priv; + + g_return_val_if_fail (app_info != NULL, NULL); + + priv = xdp_app_info_get_instance_private (app_info); + + return priv->instance; +} + +GAppInfo * +xdp_app_info_get_gappinfo (XdpAppInfo *app_info) +{ + XdpAppInfoPrivate *priv; + + g_return_val_if_fail (app_info != NULL, NULL); + + priv = xdp_app_info_get_instance_private (app_info); + + return priv->gappinfo; +} + +gboolean +xdp_app_info_is_valid_sub_app_id (XdpAppInfo *app_info, + const char *sub_app_id) +{ + if (!XDP_APP_INFO_GET_CLASS (app_info)->is_valid_sub_app_id) + return FALSE; + + return XDP_APP_INFO_GET_CLASS (app_info)->is_valid_sub_app_id (app_info, + sub_app_id); +} + +gboolean +xdp_app_info_has_network (XdpAppInfo *app_info) +{ + XdpAppInfoPrivate *priv; + + g_return_val_if_fail (app_info != NULL, TRUE); + + priv = xdp_app_info_get_instance_private (app_info); + + return (priv->flags & XDP_APP_INFO_FLAG_HAS_NETWORK) != 0; +} + +gboolean +xdp_app_info_get_pidns (XdpAppInfo *app_info, + ino_t *pidns_id_out, + GError **error) +{ + XdpAppInfoPrivate *priv = xdp_app_info_get_instance_private (app_info); + g_autoptr(GMutexLocker) guard = g_mutex_locker_new (&(priv->pidns_lock)); + ino_t ns; + + *pidns_id_out = 0; + + if (priv->pidns_id != 0) + { + *pidns_id_out = priv->pidns_id; + return TRUE; + } + + if (priv->pidfd < 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "pidns required but no pidfd provided"); + return FALSE; + } + + if (!xdp_pidfd_get_namespace (priv->pidfd, &ns, error)) + return FALSE; + + priv->pidns_id = ns; + *pidns_id_out = ns; + return TRUE; +} + +static char * +remap_path (XdpAppInfo *app_info, + const char *path) +{ + if (!XDP_APP_INFO_GET_CLASS (app_info)->remap_path) + return g_strdup (path); + + return XDP_APP_INFO_GET_CLASS (app_info)->remap_path (app_info, path); +} + +static char * +verify_proc_self_fd (XdpAppInfo *app_info, + const char *proc_path, + GError **error) +{ + char path_buffer[PATH_MAX + 1]; + ssize_t symlink_size; + int saved_errno; + + symlink_size = readlink (proc_path, path_buffer, PATH_MAX); + if (symlink_size < 0) + { + saved_errno = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), + "readlink %s: %s", proc_path, g_strerror (saved_errno)); + return NULL; + } + + path_buffer[symlink_size] = 0; + + /* All normal paths start with /, but some weird things + * don't, such as socket:[27345] or anon_inode:[eventfd]. + * We don't support any of these. + */ + if (path_buffer[0] != '/') + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, + "Not a regular file or directory: %s", path_buffer); + return NULL; + } + + /* File descriptors to actually deleted files have " (deleted)" + * appended to them. This also happens to some fake fd types + * like shmem which are "/ (deleted)". All such + * files are considered invalid. Unfortunately this also + * matches files with filenames that actually end in " (deleted)", + * but there is not much to do about this. + */ + if (g_str_has_suffix (path_buffer, " (deleted)")) + { + const char *mountpoint = xdp_get_documents_mountpoint (); + + if (mountpoint != NULL && g_str_has_prefix (path_buffer, mountpoint)) + { + /* Unfortunately our workaround for dcache purging triggers + * o_path file descriptors on the fuse filesystem being + * marked as deleted, so we have to allow these here and + * rewrite them. This is safe, becase we will stat the file + * and compare to make sure we end up on the right file. + */ + path_buffer[symlink_size - strlen (" (deleted)")] = 0; + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, + "Cannot share deleted file: %s", path_buffer); + return NULL; + } + } + + /* remap from sandbox to host if needed */ + return remap_path (app_info, path_buffer); +} + +static gboolean +check_same_file (const char *path, + struct stat *expected_st_buf, + GError **error) +{ + struct stat real_st_buf; + int saved_errno; + + if (stat (path, &real_st_buf) < 0) + { + saved_errno = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), + "stat %s: %s", path, g_strerror (saved_errno)); + return FALSE; + } + + if (expected_st_buf->st_dev != real_st_buf.st_dev || + expected_st_buf->st_ino != real_st_buf.st_ino) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "\"%s\" identity (%ju,%ju) does not match expected (%ju,%ju)", + path, + (uintmax_t) expected_st_buf->st_dev, + (uintmax_t) expected_st_buf->st_ino, + (uintmax_t) real_st_buf.st_dev, + (uintmax_t) real_st_buf.st_ino); + return FALSE; + } + + return TRUE; +} + +char * +xdp_app_info_get_path_for_fd (XdpAppInfo *app_info, + int fd, + int require_st_mode, + struct stat *st_buf, + gboolean *writable_out, + GError **error) +{ + XdpAppInfoPrivate *priv = xdp_app_info_get_instance_private (app_info); + g_autofree char *proc_path = NULL; + int fd_flags; + struct stat st_buf_store; + gboolean writable = FALSE; + g_autofree char *path = NULL; + int saved_errno; + + if (st_buf == NULL) + st_buf = &st_buf_store; + + if (fd == -1) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid file descriptor"); + return NULL; + } + + /* Must be able to get fd flags */ + fd_flags = fcntl (fd, F_GETFL); + if (fd_flags == -1) + { + saved_errno = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), + "Cannot get file descriptor flags (fcntl F_GETFL: %s)", + g_strerror (saved_errno)); + return NULL; + } + + /* Must be able to fstat */ + if (fstat (fd, st_buf) < 0) + { + saved_errno = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), + "Cannot get file information (fstat: %s)", + g_strerror (saved_errno)); + return NULL; + } + + /* Verify mode */ + if (require_st_mode != 0 && + (st_buf->st_mode & S_IFMT) != require_st_mode) + { + switch (require_st_mode) + { + case S_IFDIR: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, + "File type 0o%o is not a directory", + (st_buf->st_mode & S_IFMT)); + return NULL; + + case S_IFREG: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, + "File type 0o%o is not a regular file", + (st_buf->st_mode & S_IFMT)); + return NULL; + + default: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "File type 0o%o does not match expected 0o%o", + (st_buf->st_mode & S_IFMT), require_st_mode); + return NULL; + } + } + + proc_path = g_strdup_printf ("/proc/self/fd/%d", fd); + + /* Must be able to read valid path from /proc/self/fd */ + /* This is an absolute and (at least at open time) symlink-expanded path */ + path = verify_proc_self_fd (app_info, proc_path, error); + if (path == NULL) + return NULL; + + if ((fd_flags & O_PATH) == O_PATH) + { + int read_access_mode; + + /* Earlier versions of the portal supported only O_PATH fds, as + * these are safer to handle on the portal side. But we now + * prefer regular FDs because these ensure that the sandbox + * actually has full access to the file in its security context. + * + * However, we still support O_PATH fds when possible because + * existing code uses it. + * + * See issues #167 for details. + */ + + /* Must not be O_NOFOLLOW (because we want the target file) */ + if ((fd_flags & O_NOFOLLOW) == O_NOFOLLOW) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "O_PATH fd was opened O_NOFOLLOW"); + return NULL; + } + + if ((priv->flags & XDP_APP_INFO_FLAG_SUPPORTS_OPATH) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "App \"%s\" of type %s does not support O_PATH fd passing", + priv->id, priv->engine); + return NULL; + } + + read_access_mode = R_OK; + if (S_ISDIR (st_buf->st_mode)) + read_access_mode |= X_OK; + + /* Must be able to access the path via the sandbox supplied O_PATH fd, + which applies the sandbox side mount options (like readonly). */ + if (access (proc_path, read_access_mode) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + "\"%s\" not available for read access via \"%s\"", + path, proc_path); + return NULL; + } + + if (xdp_app_info_is_host (app_info) || access (proc_path, W_OK) == 0) + writable = TRUE; + } + else /* Regular file with no O_PATH */ + { + int accmode = fd_flags & O_ACCMODE; + + /* Note that this only gives valid results for writable for regular files, + as there is no way to get a writable fd for a directory. */ + + /* Don't allow WRONLY (or weird) open modes */ + if (accmode != O_RDONLY && + accmode != O_RDWR) + return NULL; + + if (xdp_app_info_is_host (app_info) || accmode == O_RDWR) + writable = TRUE; + } + + /* Verify that this is the same file as the app opened */ + if (!check_same_file (path, st_buf, error)) + { + /* If the path is provided by the document portal, the inode + number will not match, due to only a subtree being mounted in + the sandbox. So we check to see if the equivalent path + within that subtree matches our file descriptor. + + If the alternate path doesn't match either, then we treat it + as a failure. + */ + g_autofree char *alt_path = NULL; + alt_path = xdp_get_alternate_document_path (path, xdp_app_info_get_id (app_info)); + + if (alt_path == NULL) + return NULL; + + g_clear_error (error); + + if (!check_same_file (alt_path, st_buf, error)) + return NULL; + } + + if (writable_out) + *writable_out = writable; + + return g_steal_pointer (&path); +} + +gboolean +xdp_app_info_validate_autostart (XdpAppInfo *app_info, + GKeyFile *keyfile, + const char * const *autostart_exec, + GCancellable *cancellable, + GError **error) +{ + XdpAppInfoPrivate *priv = xdp_app_info_get_instance_private (app_info); + + if (!priv->id || + !XDP_APP_INFO_GET_CLASS (app_info)->validate_autostart) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Autostart not supported for: %s", priv->id); + return FALSE; + } + + return XDP_APP_INFO_GET_CLASS (app_info)->validate_autostart (app_info, + keyfile, + autostart_exec, + cancellable, + error); +} + +gboolean +xdp_app_info_validate_dynamic_launcher (XdpAppInfo *app_info, + GKeyFile *key_file, + GError **error) +{ + XdpAppInfoPrivate *priv = xdp_app_info_get_instance_private (app_info); + + if (!priv->id || + !XDP_APP_INFO_GET_CLASS (app_info)->validate_dynamic_launcher) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "DynamicLauncher install not supported for: %s", priv->id); + return FALSE; + } + + return XDP_APP_INFO_GET_CLASS (app_info)->validate_dynamic_launcher (app_info, + key_file, + error); +} + +const GPtrArray * +xdp_app_info_get_usb_queries (XdpAppInfo *app_info) +{ + XdpAppInfoPrivate *priv = xdp_app_info_get_instance_private (app_info); + + if (!priv->id || + !XDP_APP_INFO_GET_CLASS (app_info)->get_usb_queries) + { + return NULL; + } + + return XDP_APP_INFO_GET_CLASS (app_info)->get_usb_queries (app_info); +} + +static gboolean +xdp_connection_get_pid_legacy (GDBusConnection *connection, + const char *sender, + GCancellable *cancellable, + int *out_pidfd, + uint32_t *out_pid, + GError **error) +{ + g_autoptr(GVariant) reply = NULL; + + reply = g_dbus_connection_call_sync (connection, + DBUS_NAME_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + 30000, + cancellable, + error); + if (!reply) + return FALSE; + + *out_pidfd = -1; + g_variant_get (reply, "(u)", out_pid); + return TRUE; +} + +static gboolean +xdp_connection_get_pidfd (GDBusConnection *connection, + const char *sender, + GCancellable *cancellable, + int *out_pidfd, + uint32_t *out_pid, + GError **error) +{ + g_autoptr(GVariant) reply = NULL; + g_autoptr(GVariant) dict = NULL; + g_autoptr(GError) local_error = NULL; + g_autoptr(GVariant) process_fd = NULL; + g_autoptr(GVariant) process_id = NULL; + uint32_t pid; + int fd_index; + g_autoptr(GUnixFDList) fd_list = NULL; + g_autofd int pidfd = -1; + + reply = g_dbus_connection_call_with_unix_fd_list_sync (connection, + DBUS_NAME_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionCredentials", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(a{sv})"), + G_DBUS_CALL_FLAGS_NONE, + 30000, + NULL, + &fd_list, + cancellable, + &local_error); + + if (!reply) + { + if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE)) + { + return xdp_connection_get_pid_legacy (connection, + sender, + cancellable, + out_pidfd, + out_pid, + error); + } + + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + g_variant_get (reply, "(@a{sv})", &dict); + + process_id = g_variant_lookup_value (dict, "ProcessID", G_VARIANT_TYPE_UINT32); + if (!process_id) + { + return xdp_connection_get_pid_legacy (connection, + sender, + cancellable, + out_pidfd, + out_pid, + error); + } + + pid = g_variant_get_uint32 (process_id); + + process_fd = g_variant_lookup_value (dict, "ProcessFD", G_VARIANT_TYPE_HANDLE); + if (!process_fd) + { + *out_pidfd = -1; + *out_pid = pid; + return TRUE; + } + + fd_index = g_variant_get_handle (process_fd); + + if (fd_list == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't find peer pidfd"); + return FALSE; + } + + if (fd_index >= g_unix_fd_list_get_length (fd_list)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Pidfd index is out of bounds"); + return FALSE; + } + + pidfd = g_unix_fd_list_get (fd_list, fd_index, error); + if (pidfd < 0) + return FALSE; + + *out_pidfd = g_steal_fd (&pidfd); + *out_pid = pid; + return TRUE; +} + +static XdpAppInfo * +cache_lookup_app_info_by_sender (const char *sender) +{ + XdpAppInfo *app_info = NULL; + + G_LOCK (app_infos); + if (app_info_by_unique_name) + { + app_info = g_hash_table_lookup (app_info_by_unique_name, sender); + if (app_info) + g_object_ref (app_info); + } + G_UNLOCK (app_infos); + + return app_info; +} + +static gboolean +cache_has_app_info_by_sender (const char *sender) +{ + gboolean has_app_info = FALSE; + + G_LOCK (app_infos); + if (app_info_by_unique_name) + has_app_info = !!g_hash_table_lookup (app_info_by_unique_name, sender); + G_UNLOCK (app_infos); + + return has_app_info; +} + +static void +ensure_app_info_by_unique_name (void) +{ + if (app_info_by_unique_name == NULL) + app_info_by_unique_name = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + g_object_unref); +} + +static void +cache_insert_app_info (const char *sender, + XdpAppInfo *app_info) +{ + G_LOCK (app_infos); + ensure_app_info_by_unique_name (); + g_hash_table_insert (app_info_by_unique_name, g_strdup (sender), + g_object_ref (app_info)); + G_UNLOCK (app_infos); +} + +static void +on_peer_died (const char *name) +{ + G_LOCK (app_infos); + if (app_info_by_unique_name) + g_hash_table_remove (app_info_by_unique_name, name); + G_UNLOCK (app_infos); +} + +static XdpAppInfo * +maybe_create_test_app_info (void) +{ + const char *test_override_app_id; + const char *test_override_usb_queries; + + test_override_app_id = g_getenv ("XDG_DESKTOP_PORTAL_TEST_APP_ID"); + if (!test_override_app_id) + return NULL; + + test_override_usb_queries = g_getenv ("XDG_DESKTOP_PORTAL_TEST_USB_QUERIES"); + return xdp_app_info_test_new (test_override_app_id, + test_override_usb_queries); +} + +static XdpAppInfo * +maybe_create_registered_test_app_info (const char *registered_app_id) +{ + const char *test_override_app_id; + const char *test_override_usb_queries; + + test_override_app_id = g_getenv ("XDG_DESKTOP_PORTAL_TEST_APP_ID"); + if (!test_override_app_id) + return NULL; + + test_override_usb_queries = g_getenv ("XDG_DESKTOP_PORTAL_TEST_USB_QUERIES"); + return xdp_app_info_test_new (registered_app_id, + test_override_usb_queries); +} + +static XdpAppInfo * +xdp_connection_create_app_info_sync (GDBusConnection *connection, + const char *sender, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(XdpAppInfo) app_info = NULL; + g_autofd int pidfd = -1; + uint32_t pid; + g_autoptr(GError) local_error = NULL; + const char *app_info_kind = NULL; + + if (!xdp_connection_get_pidfd (connection, sender, cancellable, &pidfd, &pid, error)) + return NULL; + + app_info = maybe_create_test_app_info (); + if (app_info) + app_info_kind = "test"; + + if (app_info == NULL) + { + app_info = xdp_app_info_flatpak_new (pid, &pidfd, &local_error); + if (app_info) + app_info_kind = "flatpak"; + } + + if (!app_info && !g_error_matches (local_error, XDP_APP_INFO_ERROR, + XDP_APP_INFO_ERROR_WRONG_APP_KIND)) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return NULL; + } + g_clear_error (&local_error); + + if (app_info == NULL) + { + app_info = xdp_app_info_snap_new (pid, &pidfd, &local_error); + if (app_info) + app_info_kind = "snap"; + } + + if (!app_info && !g_error_matches (local_error, XDP_APP_INFO_ERROR, + XDP_APP_INFO_ERROR_WRONG_APP_KIND)) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return NULL; + } + g_clear_error (&local_error); + + if (app_info == NULL) + { + app_info = xdp_app_info_host_new (pid, &pidfd); + app_info_kind = "derived host"; + } + + g_assert (XDP_IS_APP_INFO (app_info)); + + g_debug ("Adding %s app '%s'", app_info_kind, xdp_app_info_get_id (app_info)); + + cache_insert_app_info (sender, app_info); + + xdp_connection_track_name_owners (connection, on_peer_died); + + return g_steal_pointer (&app_info); +} + +static XdpAppInfo * +xdp_connection_create_host_app_info_sync (GDBusConnection *connection, + const char *sender, + const char *app_id, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(XdpAppInfo) app_info = NULL; + g_autofd int pidfd = -1; + uint32_t pid; + + if (!xdp_connection_get_pidfd (connection, sender, cancellable, &pidfd, &pid, error)) + return NULL; + + app_info = maybe_create_registered_test_app_info (app_id); + + if (!app_info) + { + gboolean is_sandboxed = FALSE; + + if (!xdp_is_flatpak (pid, &is_sandboxed, error)) + return NULL; + + if (is_sandboxed) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't manually register a Flatpak application"); + return NULL; + } + + if (!xdp_is_snap (pid, &is_sandboxed, error)) + return NULL; + + if (is_sandboxed) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't manually register a Snap application"); + return NULL; + } + + app_info = xdp_app_info_host_new_registered (&pidfd, app_id, error); + if (!app_info) + return NULL; + } + + g_debug ("Adding registered host app '%s'", xdp_app_info_get_id (app_info)); + + cache_insert_app_info (sender, app_info); + + xdp_connection_track_name_owners (connection, on_peer_died); + + return g_steal_pointer (&app_info); +} + +XdpAppInfo * +xdp_invocation_ensure_app_info_sync (GDBusMethodInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); + const char *sender = g_dbus_method_invocation_get_sender (invocation); + g_autoptr(XdpAppInfo) app_info = NULL; + + app_info = cache_lookup_app_info_by_sender (sender); + if (app_info) + return g_steal_pointer (&app_info); + + return xdp_connection_create_app_info_sync (connection, + sender, + cancellable, + error); +} + +XdpAppInfo * +xdp_invocation_register_host_app_info_sync (GDBusMethodInvocation *invocation, + const char *app_id, + GCancellable *cancellable, + GError **error) +{ + GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); + const char *sender = g_dbus_method_invocation_get_sender (invocation); + + if (cache_has_app_info_by_sender (sender)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Connection already associated with an application ID"); + return NULL; + } + + return xdp_connection_create_host_app_info_sync (connection, + sender, + app_id, + cancellable, + error); +} diff --git a/src/xdp-app-info.h b/src/xdp-app-info.h new file mode 100644 index 0000000..60198bd --- /dev/null +++ b/src/xdp-app-info.h @@ -0,0 +1,91 @@ +/* + * Copyright © 2024 Red Hat, Inc + * Copyright © 2024 GNOME Foundation Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Hubert Figuière + */ + +#pragma once + +#include +#include + +#include +#include + +#include "glib-backports.h" + +typedef enum _XdpAppInfoError +{ + XDP_APP_INFO_ERROR_WRONG_APP_KIND, +} XdpAppInfoError; + +#define XDP_APP_INFO_ERROR (xdp_app_info_error_quark ()) +GQuark xdp_app_info_error_quark (void); + +#define XDP_TYPE_APP_INFO (xdp_app_info_get_type()) +G_DECLARE_DERIVABLE_TYPE (XdpAppInfo, + xdp_app_info, + XDP, APP_INFO, + GObject) + +gboolean xdp_app_info_is_host (XdpAppInfo *app_info); + +const char * xdp_app_info_get_id (XdpAppInfo *app_info); + +const char * xdp_app_info_get_instance (XdpAppInfo *app_info); + +GAppInfo * xdp_app_info_get_gappinfo (XdpAppInfo *app_info); + +gboolean xdp_app_info_is_valid_sub_app_id (XdpAppInfo *app_info, + const char *sub_app_id); + +gboolean xdp_app_info_has_network (XdpAppInfo *app_info); + +gboolean xdp_app_info_get_pidns (XdpAppInfo *app_info, + ino_t *pidns_id_out, + GError **error); + +char * xdp_app_info_get_path_for_fd (XdpAppInfo *app_info, + int fd, + int require_st_mode, + struct stat *st_buf, + gboolean *writable_out, + GError **error); + +gboolean xdp_app_info_validate_autostart (XdpAppInfo *app_info, + GKeyFile *keyfile, + const char * const *autostart_exec, + GCancellable *cancellable, + GError **error); + +gboolean xdp_app_info_validate_dynamic_launcher (XdpAppInfo *app_info, + GKeyFile *key_file, + GError **error); + +const GPtrArray * xdp_app_info_get_usb_queries (XdpAppInfo *app_info); + +XdpAppInfo * xdp_invocation_ensure_app_info_sync (GDBusMethodInvocation *invocation, + GCancellable *cancellable, + GError **error); + +XdpAppInfo * xdp_invocation_register_host_app_info_sync (GDBusMethodInvocation *invocation, + const char *app_id, + GCancellable *cancellable, + GError **error); diff --git a/src/xdp-app-launch-context.c b/src/xdp-app-launch-context.c new file mode 100644 index 0000000..3e709c0 --- /dev/null +++ b/src/xdp-app-launch-context.c @@ -0,0 +1,81 @@ +/* launch-context.c + * + * Copyright 2024 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "xdp-app-launch-context.h" + +struct _XdpAppLaunchContext +{ + GAppLaunchContext parent_instance; + + char *token; +}; + +G_DEFINE_TYPE (XdpAppLaunchContext, + xdp_app_launch_context, + G_TYPE_APP_LAUNCH_CONTEXT) + +void +xdp_app_launch_context_set_activation_token (XdpAppLaunchContext *self, + const char *token) +{ + g_clear_pointer (&self->token, g_free); + self->token = g_strdup (token); +} + +static char * +xdp_app_launch_context_get_startup_notify_id (GAppLaunchContext *context, + GAppInfo *info, + GList *files) +{ + XdpAppLaunchContext *self = XDP_APP_LAUNCH_CONTEXT (context); + + return g_strdup (self->token); +} + +static void +xdp_app_launch_context_finalize (GObject *object) +{ + XdpAppLaunchContext *self = XDP_APP_LAUNCH_CONTEXT (object); + + g_clear_pointer (&self->token, g_free); + + G_OBJECT_CLASS (xdp_app_launch_context_parent_class)->finalize (object); +} + +static void +xdp_app_launch_context_class_init (XdpAppLaunchContextClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GAppLaunchContextClass *g_klass = G_APP_LAUNCH_CONTEXT_CLASS (klass); + + object_class->finalize = xdp_app_launch_context_finalize; + g_klass->get_startup_notify_id = xdp_app_launch_context_get_startup_notify_id; +} + +static void +xdp_app_launch_context_init (XdpAppLaunchContext *self) +{ +} + +XdpAppLaunchContext * +xdp_app_launch_context_new (void) +{ + return g_object_new (XDP_TYPE_APP_LAUNCH_CONTEXT, NULL); +} diff --git a/src/xdp-app-launch-context.h b/src/xdp-app-launch-context.h new file mode 100644 index 0000000..3c68c6a --- /dev/null +++ b/src/xdp-app-launch-context.h @@ -0,0 +1,39 @@ +/* launch-context.h + * + * Copyright 2024 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define XDP_TYPE_APP_LAUNCH_CONTEXT (xdp_app_launch_context_get_type()) +G_DECLARE_FINAL_TYPE (XdpAppLaunchContext, + xdp_app_launch_context, + XDP, APP_LAUNCH_CONTEXT, + GAppLaunchContext) + +XdpAppLaunchContext * xdp_app_launch_context_new (void); + +void xdp_app_launch_context_set_activation_token (XdpAppLaunchContext *self, + const char *token); + +G_END_DECLS diff --git a/src/background-monitor.c b/src/xdp-background-monitor.c similarity index 77% rename from src/background-monitor.c rename to src/xdp-background-monitor.c index 687c27f..203ebe5 100644 --- a/src/background-monitor.c +++ b/src/xdp-background-monitor.c @@ -18,12 +18,12 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "background-monitor.h" +#include "xdp-background-monitor.h" #define BACKGROUND_MONITOR_BUS_NAME "org.freedesktop.background.Monitor" #define BACKGROUND_MONITOR_OBJECT_PATH "/org/freedesktop/background/monitor" -struct _BackgroundMonitor +struct _XdpBackgroundMonitor { XdpDbusBackgroundMonitorSkeleton parent_instance; @@ -32,15 +32,15 @@ struct _BackgroundMonitor static void g_initable_iface_init (GInitableIface *iface); -G_DEFINE_TYPE_WITH_CODE (BackgroundMonitor, - background_monitor, +G_DEFINE_TYPE_WITH_CODE (XdpBackgroundMonitor, + xdp_background_monitor, XDP_DBUS_BACKGROUND_TYPE_MONITOR_SKELETON, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, g_initable_iface_init)) static gboolean -request_freedesktop_background_name (BackgroundMonitor *self, - GCancellable *cancellable, - GError **error) +request_freedesktop_background_name (XdpBackgroundMonitor *self, + GCancellable *cancellable, + GError **error) { g_autoptr(GVariant) reply = NULL; GBusNameOwnerFlags flags; @@ -77,11 +77,11 @@ request_freedesktop_background_name (BackgroundMonitor *self, } static gboolean -background_monitor_initable_init (GInitable *initable, - GCancellable *cancellable, - GError **error) +xdp_background_monitor_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) { - BackgroundMonitor *self = BACKGROUND_MONITOR (initable); + XdpBackgroundMonitor *self = XDP_BACKGROUND_MONITOR (initable); g_autofree char *address = NULL; address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, cancellable, error); @@ -124,41 +124,41 @@ background_monitor_initable_init (GInitable *initable, static void g_initable_iface_init (GInitableIface *iface) { - iface->init = background_monitor_initable_init; + iface->init = xdp_background_monitor_initable_init; } static void -background_monitor_finalize (GObject *object) +xdp_background_monitor_finalize (GObject *object) { - BackgroundMonitor *self = (BackgroundMonitor *)object; + XdpBackgroundMonitor *self = XDP_BACKGROUND_MONITOR (object); if (self->connection) g_dbus_connection_flush_sync (self->connection, NULL, NULL); g_clear_object (&self->connection); - G_OBJECT_CLASS (background_monitor_parent_class)->finalize (object); + G_OBJECT_CLASS (xdp_background_monitor_parent_class)->finalize (object); } static void -background_monitor_class_init (BackgroundMonitorClass *klass) +xdp_background_monitor_class_init (XdpBackgroundMonitorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->finalize = background_monitor_finalize; + object_class->finalize = xdp_background_monitor_finalize; } static void -background_monitor_init (BackgroundMonitor *self) +xdp_background_monitor_init (XdpBackgroundMonitor *self) { xdp_dbus_background_monitor_set_version (XDP_DBUS_BACKGROUND_MONITOR (self), 1); } -BackgroundMonitor * -background_monitor_new (GCancellable *cancellable, - GError **error) +XdpBackgroundMonitor * +xdp_background_monitor_new (GCancellable *cancellable, + GError **error) { - return g_initable_new (BACKGROUND_TYPE_MONITOR, + return g_initable_new (XDP_TYPE_BACKGROUND_MONITOR, cancellable, error, NULL); diff --git a/src/background-monitor.h b/src/xdp-background-monitor.h similarity index 72% rename from src/background-monitor.h rename to src/xdp-background-monitor.h index f407183..e121c7d 100644 --- a/src/background-monitor.h +++ b/src/xdp-background-monitor.h @@ -26,13 +26,13 @@ G_BEGIN_DECLS -#define BACKGROUND_TYPE_MONITOR (background_monitor_get_type()) -G_DECLARE_FINAL_TYPE (BackgroundMonitor, - background_monitor, - BACKGROUND, MONITOR, +#define XDP_TYPE_BACKGROUND_MONITOR (xdp_background_monitor_get_type()) +G_DECLARE_FINAL_TYPE (XdpBackgroundMonitor, + xdp_background_monitor, + XDP, BACKGROUND_MONITOR, XdpDbusBackgroundMonitorSkeleton) -BackgroundMonitor *background_monitor_new (GCancellable *cancellable, - GError **error); +XdpBackgroundMonitor *xdp_background_monitor_new (GCancellable *cancellable, + GError **error); G_END_DECLS diff --git a/src/call.c b/src/xdp-call.c similarity index 51% rename from src/call.c rename to src/xdp-call.c index 781b993..5d608c1 100644 --- a/src/call.c +++ b/src/xdp-call.c @@ -1,10 +1,12 @@ /* * Copyright © 2018 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,32 +18,32 @@ * */ -#include "call.h" +#include "xdp-call.h" static void -call_free (Call *call) +xdp_call_free (XdpCall *call) { - xdp_app_info_unref (call->app_info); - g_free (call->sender); + g_clear_object (&call->app_info); + g_clear_pointer (&call->sender, g_free); g_free (call); } void -call_init_invocation (GDBusMethodInvocation *invocation, - XdpAppInfo *app_info) +xdp_call_init_invocation (GDBusMethodInvocation *invocation, + XdpAppInfo *app_info) { - Call *call; + XdpCall *call; - call = g_new0 (Call, 1); - call->app_info = xdp_app_info_ref (app_info); + call = g_new0 (XdpCall, 1); + call->app_info = g_object_ref (app_info); call->sender = g_strdup (g_dbus_method_invocation_get_sender (invocation)); - g_object_set_data_full (G_OBJECT (invocation), "call", - call, (GDestroyNotify) call_free); + g_object_set_data_full (G_OBJECT (invocation), "xdp-call", + call, (GDestroyNotify) xdp_call_free); } -Call * -call_from_invocation (GDBusMethodInvocation *invocation) +XdpCall * +xdp_call_from_invocation (GDBusMethodInvocation *invocation) { - return g_object_get_data (G_OBJECT (invocation), "call"); + return g_object_get_data (G_OBJECT (invocation), "xdp-call"); } diff --git a/src/xdp-call.h b/src/xdp-call.h new file mode 100644 index 0000000..d61f30e --- /dev/null +++ b/src/xdp-call.h @@ -0,0 +1,36 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#pragma once + +#include +#include "xdp-app-info.h" +#include "xdp-utils.h" + +typedef struct _XdpCall +{ + XdpAppInfo *app_info; + char *sender; +} XdpCall; + +void xdp_call_init_invocation (GDBusMethodInvocation *invocation, + XdpAppInfo *app_info); + +XdpCall *xdp_call_from_invocation (GDBusMethodInvocation *invocation); diff --git a/src/documents.c b/src/xdp-documents.c similarity index 84% rename from src/documents.c rename to src/xdp-documents.c index 711f96c..4913af6 100644 --- a/src/documents.c +++ b/src/xdp-documents.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -30,32 +32,46 @@ #include #include +#include "xdp-app-info.h" #include "xdp-dbus.h" #include "xdp-utils.h" -#include "documents.h" +#include "xdp-documents.h" #include "document-enums.h" static XdpDbusDocuments *documents = NULL; static char *documents_mountpoint = NULL; -void -init_document_proxy (GDBusConnection *connection) +gboolean +xdp_init_document_proxy (GDBusConnection *connection, + GError **error) { + g_autoptr(GError) local_error = NULL; + documents = xdp_dbus_documents_proxy_new_sync (connection, 0, "org.freedesktop.portal.Documents", "/org/freedesktop/portal/documents", - NULL, NULL); - xdp_dbus_documents_call_get_mount_point_sync (documents, - &documents_mountpoint, - NULL, NULL); + NULL, error); + if (!documents) + return FALSE; + + if (!xdp_dbus_documents_call_get_mount_point_sync (documents, + &documents_mountpoint, + NULL, &local_error)) + { + g_warning ("Document portal fuse mount point unknown: %s", + local_error->message); + } + xdp_set_documents_mountpoint (documents_mountpoint); + + return TRUE; } char * -register_document (const char *uri, - const char *app_id, - DocumentFlags flags, - GError **error) +xdp_register_document (const char *uri, + const char *app_id, + XdpDocumentFlags flags, + GError **error) { g_autofree char *doc_id = NULL; g_auto(GStrv) doc_ids = NULL; @@ -80,7 +96,7 @@ register_document (const char *uri, basename = g_path_get_basename (path); dirname = g_path_get_dirname (path); - if (flags & DOCUMENT_FLAG_FOR_SAVE) + if (flags & XDP_DOCUMENT_FLAG_FOR_SAVE) fd = open (dirname, O_PATH | O_CLOEXEC); else fd = open (path, O_PATH | O_CLOEXEC); @@ -100,19 +116,19 @@ register_document (const char *uri, i = 0; permissions[i++] = "read"; - if ((flags & DOCUMENT_FLAG_WRITABLE) || (flags & DOCUMENT_FLAG_FOR_SAVE)) + if ((flags & XDP_DOCUMENT_FLAG_WRITABLE) || (flags & XDP_DOCUMENT_FLAG_FOR_SAVE)) permissions[i++] = "write"; permissions[i++] = "grant-permissions"; - if (flags & DOCUMENT_FLAG_DELETABLE) + if (flags & XDP_DOCUMENT_FLAG_DELETABLE) permissions[i++] = "delete"; permissions[i++] = NULL; version = xdp_dbus_documents_get_version (documents); full_flags = DOCUMENT_ADD_FLAGS_REUSE_EXISTING | DOCUMENT_ADD_FLAGS_PERSISTENT | DOCUMENT_ADD_FLAGS_AS_NEEDED_BY_APP; - if (flags & DOCUMENT_FLAG_DIRECTORY) + if (flags & XDP_DOCUMENT_FLAG_DIRECTORY) full_flags |= DOCUMENT_ADD_FLAGS_DIRECTORY; - if (flags & DOCUMENT_FLAG_FOR_SAVE) + if (flags & XDP_DOCUMENT_FLAG_FOR_SAVE) { if (version >= 3) { @@ -202,8 +218,8 @@ register_document (const char *uri, } char * -get_real_path_for_doc_path (const char *path, - XdpAppInfo *app_info) +xdp_get_real_path_for_doc_path (const char *path, + XdpAppInfo *app_info) { g_autofree char *doc_id = NULL; gboolean ret = FALSE; @@ -225,11 +241,11 @@ get_real_path_for_doc_path (const char *path, return g_strdup (path); } - return get_real_path_for_doc_id (doc_id); + return xdp_get_real_path_for_doc_id (doc_id); } char * -get_real_path_for_doc_id (const char *doc_id) +xdp_get_real_path_for_doc_id (const char *doc_id) { gboolean ret = FALSE; char *real_path = NULL; diff --git a/src/xdp-documents.h b/src/xdp-documents.h new file mode 100644 index 0000000..dda9240 --- /dev/null +++ b/src/xdp-documents.h @@ -0,0 +1,46 @@ +/* + * Copyright © 2016 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Matthias Clasen + */ + +#pragma once + +#include + +typedef enum { + XDP_DOCUMENT_FLAG_NONE = 0, + XDP_DOCUMENT_FLAG_FOR_SAVE = (1 << 0), + XDP_DOCUMENT_FLAG_WRITABLE = (1 << 1), + XDP_DOCUMENT_FLAG_DIRECTORY = (1 << 2), + XDP_DOCUMENT_FLAG_DELETABLE = (1 << 3), +} XdpDocumentFlags; + +gboolean xdp_init_document_proxy (GDBusConnection *connection, + GError **error); + +char *xdp_register_document (const char *uri, + const char *app_id, + XdpDocumentFlags flags, + GError **error); + +char *xdp_get_real_path_for_doc_path (const char *path, + XdpAppInfo *app_info); + +char *xdp_get_real_path_for_doc_id (const char *doc_id); diff --git a/src/xdp-method-info.c b/src/xdp-method-info.c new file mode 100644 index 0000000..5e035f6 --- /dev/null +++ b/src/xdp-method-info.c @@ -0,0 +1,49 @@ +/* + * Copyright © 2023 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#include "config.h" + +#include + +#include "xdp-method-info.h" + +const XdpMethodInfo * +xdp_method_info_find (const char *interface, + const char *method) +{ + const XdpMethodInfo *mi = NULL; + gboolean interface_found = FALSE; + + mi = xdp_method_info_get_all (); + while (mi->interface != NULL) + { + if (strcmp (interface, mi->interface) == 0) + { + interface_found = TRUE; + if (strcmp (method, mi->method) == 0) + return mi; + } + else if (interface_found) + break; + mi++; + } + + return NULL; +} diff --git a/src/xdp-method-info.h b/src/xdp-method-info.h new file mode 100644 index 0000000..b45ec35 --- /dev/null +++ b/src/xdp-method-info.h @@ -0,0 +1,40 @@ +/* + * Copyright © 2023 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#pragma once + +#include + +typedef struct { + const char *interface; + const char *method; + gboolean uses_request; + int option_arg; +} XdpMethodInfo; + +const XdpMethodInfo * +xdp_method_info_find (const char *interface, + const char *method); + +const XdpMethodInfo * +xdp_method_info_get_all (void); + +unsigned int +xdp_method_info_get_count (void); diff --git a/src/permissions.c b/src/xdp-permissions.c similarity index 71% rename from src/permissions.c rename to src/xdp-permissions.c index ff09c87..d2a6437 100644 --- a/src/permissions.c +++ b/src/xdp-permissions.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -22,14 +24,14 @@ #include -#include "permissions.h" +#include "xdp-permissions.h" static XdpDbusImplPermissionStore *permission_store = NULL; char ** -get_permissions_sync (const char *app_id, - const char *table, - const char *id) +xdp_get_permissions_sync (const char *app_id, + const char *table, + const char *id) { g_autoptr(GError) error = NULL; g_autoptr(GVariant) out_perms = NULL; @@ -59,48 +61,48 @@ get_permissions_sync (const char *app_id, return g_strdupv (permissions); } -Permission -permissions_to_tristate (char **permissions) +XdpPermission +xdp_permissions_to_tristate (char **permissions) { if (g_strv_length ((char **)permissions) != 1) { g_autofree char *a = g_strjoinv (" ", (char **)permissions); g_warning ("Wrong permission format, ignoring (%s)", a); - return PERMISSION_UNSET; + return XDP_PERMISSION_UNSET; } if (strcmp (permissions[0], "yes") == 0) - return PERMISSION_YES; + return XDP_PERMISSION_YES; else if (strcmp (permissions[0], "no") == 0) - return PERMISSION_NO; + return XDP_PERMISSION_NO; else if (strcmp (permissions[0], "ask") == 0) - return PERMISSION_ASK; + return XDP_PERMISSION_ASK; else { g_autofree char *a = g_strjoinv (" ", (char **)permissions); g_warning ("Wrong permission format, ignoring (%s)", a); } - return PERMISSION_UNSET; + return XDP_PERMISSION_UNSET; } char ** -permissions_from_tristate (Permission permission) +xdp_permissions_from_tristate (XdpPermission permission) { char *permission_str; char **permissions; switch (permission) { - case PERMISSION_UNSET: + case XDP_PERMISSION_UNSET: return NULL; - case PERMISSION_NO: + case XDP_PERMISSION_NO: permission_str = g_strdup ("no"); break; - case PERMISSION_YES: + case XDP_PERMISSION_YES: permission_str = g_strdup ("yes"); break; - case PERMISSION_ASK: + case XDP_PERMISSION_ASK: permission_str = g_strdup ("ask"); break; default: @@ -115,10 +117,10 @@ permissions_from_tristate (Permission permission) } void -set_permissions_sync (const char *app_id, - const char *table, - const char *id, - const char * const *permissions) +xdp_set_permissions_sync (const char *app_id, + const char *table, + const char *id, + const char * const *permissions) { g_autoptr(GError) error = NULL; @@ -136,47 +138,46 @@ set_permissions_sync (const char *app_id, } } -Permission -get_permission_sync (const char *app_id, - const char *table, - const char *id) +XdpPermission +xdp_get_permission_sync (const char *app_id, + const char *table, + const char *id) { g_auto(GStrv) perms = NULL; - perms = get_permissions_sync (app_id, table, id); + perms = xdp_get_permissions_sync (app_id, table, id); if (perms) - return permissions_to_tristate (perms); + return xdp_permissions_to_tristate (perms); - return PERMISSION_UNSET; + return XDP_PERMISSION_UNSET; } -void set_permission_sync (const char *app_id, - const char *table, - const char *id, - Permission permission) +void +xdp_set_permission_sync (const char *app_id, + const char *table, + const char *id, + XdpPermission permission) { g_auto(GStrv) perms = NULL; - perms = permissions_from_tristate (permission); - set_permissions_sync (app_id, table, id, (const char * const *)perms); + perms = xdp_permissions_from_tristate (permission); + xdp_set_permissions_sync (app_id, table, id, (const char * const *)perms); } -void -init_permission_store (GDBusConnection *connection) +gboolean +xdp_init_permission_store (GDBusConnection *connection, + GError **error) { - g_autoptr(GError) error = NULL; - permission_store = xdp_dbus_impl_permission_store_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, "org.freedesktop.impl.portal.PermissionStore", "/org/freedesktop/impl/portal/PermissionStore", - NULL, &error); - if (permission_store == NULL) - g_warning ("No permission store: %s", error->message); + NULL, error); + return (permission_store != NULL); } XdpDbusImplPermissionStore * -get_permission_store (void) +xdp_get_permission_store (void) { return permission_store; } diff --git a/src/xdp-permissions.h b/src/xdp-permissions.h new file mode 100644 index 0000000..d0c6800 --- /dev/null +++ b/src/xdp-permissions.h @@ -0,0 +1,61 @@ +/* + * Copyright © 2016 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Matthias Clasen + */ + +#pragma once + +#include +#include "xdp-impl-dbus.h" + +typedef enum _XdpPermission +{ + XDP_PERMISSION_UNSET, + XDP_PERMISSION_NO, + XDP_PERMISSION_YES, + XDP_PERMISSION_ASK +} XdpPermission; + +char **xdp_get_permissions_sync (const char *app_id, + const char *table, + const char *id); + +void xdp_set_permissions_sync (const char *app_id, + const char *table, + const char *id, + const char * const *permissions); + +XdpPermission xdp_get_permission_sync (const char *app_id, + const char *table, + const char *id); + +void xdp_set_permission_sync (const char *app_id, + const char *table, + const char *id, + XdpPermission permission); + +char **xdp_permissions_from_tristate (XdpPermission permission); + +XdpPermission xdp_permissions_to_tristate (char **permissions); + +gboolean xdp_init_permission_store (GDBusConnection *connection, + GError **err); + +XdpDbusImplPermissionStore *xdp_get_permission_store (void); diff --git a/src/portal-impl.c b/src/xdp-portal-impl.c similarity index 59% rename from src/portal-impl.c rename to src/xdp-portal-impl.c index 8c1fd53..27ffd22 100644 --- a/src/portal-impl.c +++ b/src/xdp-portal-impl.c @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -21,7 +23,7 @@ #include "config.h" -#include "portal-impl.h" +#include "xdp-portal-impl.h" #include #include @@ -42,6 +44,8 @@ typedef struct _PortalConfig { PortalInterface *default_portal; } PortalConfig; +#define XDP_SUBDIR "xdg-desktop-portal" + static void portal_interface_free (PortalInterface *iface) { @@ -66,7 +70,7 @@ portal_config_free (PortalConfig *config) } static void -portal_implementation_free (PortalImplementation *impl) +portal_implementation_free (XdpPortalImplementation *impl) { g_clear_pointer (&impl->source, g_free); g_clear_pointer (&impl->dbus_name, g_free); @@ -75,7 +79,7 @@ portal_implementation_free (PortalImplementation *impl) g_free (impl); } -G_DEFINE_AUTOPTR_CLEANUP_FUNC(PortalImplementation, portal_implementation_free) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(XdpPortalImplementation, portal_implementation_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(PortalInterface, portal_interface_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(PortalConfig, portal_config_free) @@ -107,13 +111,13 @@ validate_xdg_desktop (const char *desktop) } static char ** -get_valid_current_desktops (const char *value) +get_valid_current_desktops (void) { GPtrArray *valid_desktops; + const char *value; char **tmp; - if (value == NULL) - value = g_getenv ("XDG_CURRENT_DESKTOP"); + value = g_getenv ("XDG_CURRENT_DESKTOP"); if (value == NULL) value = ""; @@ -144,7 +148,7 @@ get_current_lowercase_desktops (void) if (g_once_init_enter (&result)) { - char **tmp = get_valid_current_desktops (NULL); + char **tmp = get_valid_current_desktops (); for (size_t i = 0; tmp[i] != NULL; i++) { @@ -161,16 +165,18 @@ get_current_lowercase_desktops (void) /* }}} */ static PortalConfig *config = NULL; -static GList *implementations = NULL; +static GPtrArray *implementations = NULL; static gboolean -register_portal (const char *path, +register_portal (GHashTable *portals, + const char *path, gboolean opt_verbose, GError **error) { - g_autoptr(PortalImplementation) impl = g_new0 (PortalImplementation, 1); + g_autoptr(XdpPortalImplementation) impl = g_new0 (XdpPortalImplementation, 1); g_autoptr(GKeyFile) keyfile = g_key_file_new (); g_autofree char *basename = NULL; + g_autofree char *source = NULL; int i; g_debug ("loading %s", path); @@ -179,10 +185,18 @@ register_portal (const char *path, return FALSE; basename = g_path_get_basename (path); - impl->source = g_strndup (basename, strrchr (basename, '.') - basename); + source = g_strndup (basename, strrchr (basename, '.') - basename); + impl->source = g_strdup (source); + if (g_hash_table_contains (portals, impl->source)) + { + g_debug ("Skipping duplicate source %s", source); + return TRUE; + } + impl->dbus_name = g_key_file_get_string (keyfile, "portal", "DBusName", error); if (impl->dbus_name == NULL) return FALSE; + if (!g_dbus_is_name (impl->dbus_name)) { g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, @@ -193,6 +207,7 @@ register_portal (const char *path, impl->interfaces = g_key_file_get_string_list (keyfile, "portal", "Interfaces", NULL, error); if (impl->interfaces == NULL) return FALSE; + for (i = 0; impl->interfaces[i]; i++) { if (!g_dbus_is_interface_name (impl->interfaces[i])) @@ -216,9 +231,10 @@ register_portal (const char *path, } impl->use_in = g_key_file_get_string_list (keyfile, "portal", "UseIn", NULL, error); - implementations = g_list_prepend (implementations, impl); - impl = NULL; + g_hash_table_insert (portals, + g_steal_pointer (&source), + g_steal_pointer (&impl)); return TRUE; } @@ -239,8 +255,8 @@ static gint sort_impl_by_use_in_and_name (gconstpointer a, gconstpointer b) { - const PortalImplementation *pa = a; - const PortalImplementation *pb = b; + const XdpPortalImplementation *pa = a; + const XdpPortalImplementation *pb = b; const char **desktops; int i; @@ -266,17 +282,13 @@ sort_impl_by_use_in_and_name (gconstpointer a, return strcmp (pa->source, pb->source); } -void -load_installed_portals (gboolean opt_verbose) +static void +load_installed_portals_dir (GHashTable *portals, + const char *portal_dir, + gboolean opt_verbose) { g_autoptr(GFileEnumerator) enumerator = NULL; g_autoptr(GFile) dir = NULL; - const char *portal_dir; - - /* We need to override this in the tests */ - portal_dir = g_getenv ("XDG_DESKTOP_PORTAL_DIR"); - if (portal_dir == NULL) - portal_dir = DATADIR "/xdg-desktop-portal/portals"; g_debug ("load portals from %s", portal_dir); @@ -307,14 +319,93 @@ load_installed_portals (gboolean opt_verbose) child = g_file_enumerator_get_child (enumerator, info); path = g_file_get_path (child); - if (!register_portal (path, opt_verbose, &error)) + if (!register_portal (portals, path, opt_verbose, &error)) { g_warning ("Error loading %s: %s", path, error->message); continue; } } +} + +#if !GLIB_CHECK_VERSION(2, 76, 0) +static gboolean +steal_portal_impl_foreach (gpointer key, + gpointer value, + gpointer user_data) +{ + GPtrArray *implementations = user_data; + + g_free (key); + g_ptr_array_add (implementations, value); + /* declare that we took ownership */ + return TRUE; +} + +static int +sort_impl_by_use_in_and_name_indirect (gconstpointer a, + gconstpointer b) +{ + const XdpPortalImplementation * const *left = a; + const XdpPortalImplementation * const *right = b; + + return sort_impl_by_use_in_and_name (*left, *right); +} +#endif + +void +load_installed_portals (gboolean opt_verbose) +{ + g_autoptr (GHashTable) portals = NULL; + const char *portal_dir; + g_autofree char *user_portal_dir = NULL; + const char * const *dirs; + const char * const *iter; + + portals = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + (GDestroyNotify) portal_implementation_free); + + /* We need to override this in the tests */ + portal_dir = g_getenv ("XDG_DESKTOP_PORTAL_DIR"); + if (portal_dir != NULL) + { + load_installed_portals_dir (portals, portal_dir, opt_verbose); + /* All other config directories are ignored when this is set */ + goto out; + } + + /* $XDG_DATA_HOME/xdg-desktop-portal/portals */ + user_portal_dir = g_build_filename (g_get_user_data_dir (), XDP_SUBDIR, "portals", NULL); + load_installed_portals_dir (portals, user_portal_dir, opt_verbose); + + /* $XDG_DATA_DIRS/xdg-desktop-portal/portals */ + dirs = g_get_system_data_dirs (); + + for (iter = dirs; iter != NULL && *iter != NULL; iter++) + { + g_autofree char *dir = NULL; + + dir = g_build_filename (*iter, XDP_SUBDIR, "portals", NULL); + load_installed_portals_dir (portals, dir, opt_verbose); + } + + /* ${datadir}/xdg-desktop-portal/portals */ + portal_dir = DATADIR "/" XDP_SUBDIR "/portals"; + load_installed_portals_dir (portals, portal_dir, opt_verbose); - implementations = g_list_sort (implementations, sort_impl_by_use_in_and_name); +out: + + g_clear_pointer (&implementations, g_ptr_array_unref); + +#if GLIB_CHECK_VERSION(2, 76, 0) + implementations = g_hash_table_steal_all_values (portals); + g_ptr_array_sort_values (implementations, sort_impl_by_use_in_and_name); +#else + implementations = g_ptr_array_new_full (g_hash_table_size (portals), + (GDestroyNotify) portal_implementation_free); + g_hash_table_foreach_steal (portals, steal_portal_impl_foreach, implementations); + g_ptr_array_sort (implementations, sort_impl_by_use_in_and_name_indirect); +#endif } static PortalConfig * @@ -363,9 +454,16 @@ load_portal_configuration_for_dir (gboolean opt_verbose, } if (strcmp (ifaces[i], "default") == 0) - default_portal = g_steal_pointer (&interface); + { + if (default_portal == NULL) + default_portal = g_steal_pointer (&interface); + else + g_warning ("Duplicate default key will get ignored"); + } else - g_ptr_array_add (interfaces, g_steal_pointer (&interface)); + { + g_ptr_array_add (interfaces, g_steal_pointer (&interface)); + } } portal_config->n_ifaces = interfaces->len; @@ -420,8 +518,6 @@ load_config_directory (const char *dir, return FALSE; } -#define XDP_SUBDIR "xdg-desktop-portal" - void load_portal_configuration (gboolean opt_verbose) { @@ -488,7 +584,7 @@ load_portal_configuration (gboolean opt_verbose) return; } -PortalInterface * +static PortalInterface * find_matching_iface_config (const char *interface) { if (config == NULL) @@ -535,83 +631,224 @@ portal_interface_prefers_none (const char *interface) } static gboolean -portal_impl_name_matches (const PortalImplementation *impl, - const PortalInterface *iface) +portal_impl_supports_iface (const XdpPortalImplementation *impl, + const char *interface) +{ + return g_strv_contains ((const char * const *) impl->interfaces, interface); +} + +static void +warn_please_use_portals_conf (void) +{ + g_warning_once ("The preferred method to match portal implementations " + "to desktop environments is to use the portals.conf(5) " + "configuration file"); +} + +static XdpPortalImplementation * +find_any_portal_implementation (const char *interface) { - /* Exact match */ - if (g_strv_contains ((const char * const *) iface->portals, impl->source)) + for (size_t i = 0; i < implementations->len; i++) { - g_debug ("Found '%s' in configuration for %s", impl->source, iface->dbus_name); - return TRUE; + XdpPortalImplementation *impl = g_ptr_array_index (implementations, i); + + if (!portal_impl_supports_iface (impl, interface)) + continue; + + g_debug ("Falling back to %s.portal for %s", impl->source, interface); + return impl; } - /* The "*" alias means "any" */ - if (g_strv_contains ((const char * const *) iface->portals, "*")) + return NULL; +} + +static XdpPortalImplementation * +find_portal_implementation_by_name (const char *portal_name) +{ + if (portal_name == NULL) + return NULL; + + for (size_t i = 0; i < implementations->len; i++) { - g_debug ("Found '*' in configuration for %s", iface->dbus_name); - return TRUE; + XdpPortalImplementation *impl = g_ptr_array_index (implementations, i); + + if (g_str_equal (impl->source, portal_name)) + return impl; } - /* No portal */ - if (portal_interface_prefers_none (iface->dbus_name)) + g_debug ("Requested %s.portal is unrecognized", portal_name); + return NULL; +} + +static XdpPortalImplementation * +find_portal_implementation_iface (const PortalInterface *iface) +{ + if (iface == NULL) + return NULL; + + for (size_t i = 0; iface->portals && iface->portals[i]; i++) { - g_debug ("Found 'none' in configuration for %s", iface->dbus_name); - return FALSE; + XdpPortalImplementation *impl; + const char *portal = iface->portals[i]; + + g_debug ("Found '%s' in configuration for %s", portal, iface->dbus_name); + + if (g_str_equal (portal, "*")) + return find_any_portal_implementation (iface->dbus_name); + + impl = find_portal_implementation_by_name (portal); + + if (!impl) + { + g_info ("Requested backend %s does not exist. Skipping...", portal); + continue; + } + + if (!portal_impl_supports_iface (impl, iface->dbus_name)) + { + g_info ("Requested backend %s.portal does not support %s. Skipping...", impl->source, iface->dbus_name); + continue; + } + + return impl; } - return FALSE; + return NULL; } -static gboolean -portal_impl_matches_config (const PortalImplementation *impl, - const char *interface) +static void +_add_all_portal_implementations_iface (const PortalInterface *iface, + const char *interface, + GPtrArray *impls) { - if (config == NULL) - return FALSE; + g_autofree char *portals = NULL; - /* Interfaces have precedence, followed by the "default" catch all, - * to allow for specific interfaces to override the default - */ - for (int i = 0; i < config->n_ifaces; i++) + portals = g_strjoinv (";", iface->portals); + g_debug ("Found '%s' in configuration for %s", portals, iface->dbus_name); + + for (size_t i = 0; iface->portals && iface->portals[i]; i++) { - const PortalInterface *iface = config->interfaces[i]; + const char *portal = iface->portals[i]; - if (g_strcmp0 (iface->dbus_name, interface) == 0) - return portal_impl_name_matches (impl, iface); + for (size_t j = 0; j < implementations->len; j++) + { + XdpPortalImplementation *impl = g_ptr_array_index (implementations, j); + + if (!g_str_equal (impl->source, portal) && !g_str_equal (portal, "*")) + continue; + + if (g_ptr_array_find (impls, impl, NULL)) + { + g_info ("Duplicate backend %s.portal. Skipping...", impl->source); + continue; + } + + if (!portal_impl_supports_iface (impl, interface)) + { + g_info ("Requested backend %s.portal does not support %s. Skipping...", + impl->source, interface); + continue; + } + + g_debug ("Using %s.portal for %s (config)", impl->source, interface); + g_ptr_array_add (impls, impl); + } } +} + +static void +add_all_portal_implementations_iface (const PortalInterface *iface, + GPtrArray *impls) +{ + if (iface == NULL) + return; - if (config->default_portal) - return portal_impl_name_matches (impl, config->default_portal); + _add_all_portal_implementations_iface (iface, iface->dbus_name, impls); +} - return FALSE; +static XdpPortalImplementation * +find_default_implementation_iface (const char *interface) +{ + PortalInterface *iface; + + if (config == NULL || config->default_portal == NULL) + return NULL; + + iface = config->default_portal; + + for (size_t i = 0; iface->portals && iface->portals[i]; i++) + { + XdpPortalImplementation *impl; + const char *portal = iface->portals[i]; + + g_debug ("Found '%s' in configuration for default", portal); + + if (g_str_equal (portal, "*")) + return find_any_portal_implementation (iface->dbus_name); + + impl = find_portal_implementation_by_name (portal); + + if (impl && portal_impl_supports_iface (impl, interface)) + return impl; + } + return NULL; } static void -warn_please_use_portals_conf (void) +add_all_default_portal_implementations_iface (const char *interface, + GPtrArray *impls) { - g_warning_once ("The preferred method to match portal implementations " - "to desktop environments is to use the portals.conf(5) " - "configuration file"); + if (config == NULL || config->default_portal == NULL) + return; + + _add_all_portal_implementations_iface (config->default_portal, interface, impls); } -PortalImplementation * +static XdpPortalImplementation * +find_gtk_fallback_portal_implementation (const char *interface) +{ + /* As a last resort, if nothing was selected for this desktop by + * ${desktop}-portals.conf or portals.conf, and no portal volunteered + * itself as suitable for this desktop via the legacy UseIn mechanism, + * try to fall back to x-d-p-gtk, which has historically been the portal + * UI backend used by desktop environments with no backend of their own. + * If it isn't installed, that is not an error: we just don't use it. */ + for (size_t i = 0; i < implementations->len; i++) + { + XdpPortalImplementation *impl = g_ptr_array_index (implementations, i); + + if (!g_str_equal (impl->dbus_name, "org.freedesktop.impl.portal.desktop.gtk")) + continue; + + if (!portal_impl_supports_iface (impl, interface)) + continue; + + g_warning ("Choosing %s.portal for %s as a last-resort fallback", + impl->source, interface); + + return impl; + } + + return NULL; +} + +XdpPortalImplementation * find_portal_implementation (const char *interface) { const char **desktops; - GList *l; - int i; if (portal_interface_prefers_none (interface)) return NULL; - for (l = implementations; l != NULL; l = l->next) + if (config) { - PortalImplementation *impl = l->data; + PortalInterface *iface = find_matching_iface_config (interface); + XdpPortalImplementation *impl = find_portal_implementation_iface (iface); - if (!g_strv_contains ((const char **)impl->interfaces, interface)) - continue; + if (!impl) + impl = find_default_implementation_iface (interface); - if (portal_impl_matches_config (impl, interface)) + if (impl != NULL) { g_debug ("Using %s.portal for %s (config)", impl->source, interface); return impl; @@ -621,13 +858,13 @@ find_portal_implementation (const char *interface) desktops = get_current_lowercase_desktops (); /* Fallback to the old UseIn key */ - for (i = 0; desktops[i] != NULL; i++) + for (size_t i = 0; desktops[i] != NULL; i++) { - for (l = implementations; l != NULL; l = l->next) + for (size_t j = 0; j < implementations->len; j++) { - PortalImplementation *impl = l->data; + XdpPortalImplementation *impl = g_ptr_array_index (implementations, j); - if (!g_strv_contains ((const char **)impl->interfaces, interface)) + if (!portal_impl_supports_iface (impl, interface)) continue; if (impl->use_in != NULL && g_strv_case_contains ((const char **)impl->use_in, desktops[i])) @@ -641,55 +878,60 @@ find_portal_implementation (const char *interface) } } - /* As a last resort, if nothing was selected for this desktop by - * ${desktop}-portals.conf or portals.conf, and no portal volunteered - * itself as suitable for this desktop via the legacy UseIn mechanism, - * try to fall back to x-d-p-gtk, which has historically been the portal - * UI backend used by desktop environments with no backend of their own. - * If it isn't installed, that is not an error: we just don't use it. */ - for (l = implementations; l != NULL; l = l->next) - { - PortalImplementation *impl = l->data; - - if (!g_str_equal (impl->dbus_name, "org.freedesktop.impl.portal.desktop.gtk")) - continue; - - if (!g_strv_contains ((const char **)impl->interfaces, interface)) - continue; - - g_warning ("Choosing %s.portal for %s as a last-resort fallback", - impl->source, interface); - warn_please_use_portals_conf (); - return impl; - } - - return NULL; + return find_gtk_fallback_portal_implementation (interface); } GPtrArray * find_all_portal_implementations (const char *interface) { - GPtrArray *impls; - GList *l; + const char **desktops; + PortalInterface *iface; + XdpPortalImplementation *gtk_fallback; + g_autoptr(GPtrArray) impls = NULL; impls = g_ptr_array_new (); if (portal_interface_prefers_none (interface)) - return impls; + return g_steal_pointer (&impls); - for (l = implementations; l != NULL; l = l->next) - { - PortalImplementation *impl = l->data; + iface = find_matching_iface_config (interface); + add_all_portal_implementations_iface (iface, impls); + if (impls->len > 0) + return g_steal_pointer (&impls); - if (!g_strv_contains ((const char **)impl->interfaces, interface)) - continue; + add_all_default_portal_implementations_iface (interface, impls); + if (impls->len > 0) + return g_steal_pointer (&impls); + + desktops = get_current_lowercase_desktops (); - if (portal_impl_matches_config (impl, interface)) + /* Fallback to the old UseIn key */ + for (size_t i = 0; desktops[i] != NULL; i++) + { + for (size_t j = 0; j < implementations->len; j++) { - g_debug ("Using %s.portal for %s (config)", impl->source, interface); - g_ptr_array_add (impls, impl); + XdpPortalImplementation *impl = g_ptr_array_index (implementations, j); + + if (!portal_impl_supports_iface (impl, interface)) + continue; + + if (impl->use_in != NULL && g_strv_case_contains ((const char **)impl->use_in, desktops[i])) + { + g_warning ("Choosing %s.portal for %s via the deprecated UseIn key", + impl->source, interface); + warn_please_use_portals_conf (); + g_debug ("Using %s.portal for %s in %s (fallback)", impl->source, interface, desktops[i]); + g_ptr_array_add (impls, impl); + } } } - return impls; + if (impls->len > 0) + return g_steal_pointer (&impls); + + gtk_fallback = find_gtk_fallback_portal_implementation (interface); + if (gtk_fallback) + g_ptr_array_add (impls, gtk_fallback); + + return g_steal_pointer (&impls); } diff --git a/src/portal-impl.h b/src/xdp-portal-impl.h similarity index 64% rename from src/portal-impl.h rename to src/xdp-portal-impl.h index 4c82ac2..7ad607a 100644 --- a/src/portal-impl.h +++ b/src/xdp-portal-impl.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -19,8 +21,7 @@ * Matthias Clasen */ -#ifndef __PORTAL_IMPL_H__ -#define __PORTAL_IMPL_H__ +#pragma once #include @@ -30,11 +31,10 @@ typedef struct { char **interfaces; char **use_in; int priority; -} PortalImplementation; +} XdpPortalImplementation; -void load_installed_portals (gboolean opt_verbose); -void load_portal_configuration (gboolean opt_verbose); -PortalImplementation *find_portal_implementation (const char *interface); -GPtrArray *find_all_portal_implementations (const char *interface); +void load_installed_portals (gboolean opt_verbose); +void load_portal_configuration (gboolean opt_verbose); +XdpPortalImplementation *find_portal_implementation (const char *interface); +GPtrArray *find_all_portal_implementations (const char *interface); -#endif /* __PORTAL_IMPL_H__ */ diff --git a/src/xdp-request.c b/src/xdp-request.c new file mode 100644 index 0000000..6aaa7ed --- /dev/null +++ b/src/xdp-request.c @@ -0,0 +1,357 @@ +/* + * Copyright © 2016 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Alexander Larsson + * Matthias Clasen + */ + +#include "xdp-request.h" +#include "xdp-utils.h" +#include "xdp-method-info.h" + +#include + +static void xdp_request_skeleton_iface_init (XdpDbusRequestIface *iface); + +G_DEFINE_TYPE_WITH_CODE (XdpRequest, xdp_request, XDP_DBUS_TYPE_REQUEST_SKELETON, + G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_REQUEST, + xdp_request_skeleton_iface_init)) + +static void +xdp_request_on_signal_response (XdpDbusRequest *object, + guint arg_response, + GVariant *arg_results) +{ + XdpRequest *request = XDP_REQUEST (object); + XdpDbusRequestSkeleton *skeleton = XDP_DBUS_REQUEST_SKELETON (object); + GList *connections, *l; + GVariant *signal_variant; + + connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton)); + + signal_variant = g_variant_ref_sink (g_variant_new ("(u@a{sv})", + arg_response, + arg_results)); + for (l = connections; l != NULL; l = l->next) + { + GDBusConnection *connection = l->data; + g_dbus_connection_emit_signal (connection, + request->sender, + g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), + "org.freedesktop.portal.Request", + "Response", + signal_variant, + NULL); + } + g_variant_unref (signal_variant); + g_list_free_full (connections, g_object_unref); +} + +static gboolean +xd_request_handle_close (XdpDbusRequest *object, + GDBusMethodInvocation *invocation) +{ + XdpRequest *request = XDP_REQUEST (object); + g_autoptr(GError) error = NULL; + + g_debug ("Handling Close"); + REQUEST_AUTOLOCK (request); + + if (request->exported) + { + if (request->impl_request && + !xdp_dbus_impl_request_call_close_sync (request->impl_request, + NULL, &error)) + { + if (invocation) + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + xdp_request_unexport (request); + } + + if (invocation) + xdp_dbus_request_complete_close (XDP_DBUS_REQUEST (request), invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +xdp_request_skeleton_iface_init (XdpDbusRequestIface *iface) +{ + iface->handle_close = xd_request_handle_close; + iface->response = xdp_request_on_signal_response; +} + +G_LOCK_DEFINE (requests); +static GHashTable *requests; + +static void +xdp_request_init (XdpRequest *request) +{ + g_mutex_init (&request->mutex); +} + +static void +xdp_request_finalize (GObject *object) +{ + XdpRequest *request = XDP_REQUEST (object); + + G_LOCK (requests); + g_hash_table_remove (requests, request->id); + G_UNLOCK (requests); + + g_clear_object (&request->impl_request); + g_clear_pointer (&request->sender, g_free); + g_clear_pointer (&request->id, g_free); + g_mutex_clear (&request->mutex); + g_clear_object (&request->app_info); + + G_OBJECT_CLASS (xdp_request_parent_class)->finalize (object); +} + +static void +xdp_request_class_init (XdpRequestClass *klass) +{ + GObjectClass *gobject_class; + + requests = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, NULL); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = xdp_request_finalize; +} + +static gboolean +request_authorize_callback (GDBusInterfaceSkeleton *interface, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + const gchar *request_sender = user_data; + const gchar *sender = g_dbus_method_invocation_get_sender (invocation); + + if (strcmp (sender, request_sender) != 0) + { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Portal operation not allowed: Unmatched caller"); + return FALSE; + } + + return TRUE; +} + +/* This is a bit ugly - we need to know where the options vardict is + * in the parameters for each request. Instead of inventing some + * complicated mechanism for each implementation to provide that + * information, just hardcode it here for now. + * + * Note that the pointer returned by this function is good to use + * as long as the invocation object exists, since it points at data + * in the parameters variant. + */ +static const char * +get_token (GDBusMethodInvocation *invocation) +{ + const char *interface; + const char *method; + GVariant *parameters; + g_autoptr(GVariant) options = NULL; + const char *token = NULL; + const XdpMethodInfo *method_info; + + interface = g_dbus_method_invocation_get_interface_name (invocation); + method = g_dbus_method_invocation_get_method_name (invocation); + parameters = g_dbus_method_invocation_get_parameters (invocation); + + method_info = xdp_method_info_find (interface, method); + if (method_info) + { + if (method_info->option_arg >= 0) + options = g_variant_get_child_value (parameters, method_info->option_arg); + } + else + { + g_warning ("Support for %s::%s missing in %s", + interface, method, G_STRLOC); + } + + if (options) + g_variant_lookup (options, "handle_token", "&s", &token); + + return token ? token : "t"; +} + +void +xdp_request_init_invocation (GDBusMethodInvocation *invocation, + XdpAppInfo *app_info) +{ + XdpRequest *request; + guint32 r; + char *id = NULL; + const char *token; + g_autofree char *sender = NULL; + int i; + + request = g_object_new (xdp_request_get_type (), NULL); + request->sender = g_strdup (g_dbus_method_invocation_get_sender (invocation)); + request->app_info = g_object_ref (app_info); + + g_object_set_data (G_OBJECT (request), "fd", GINT_TO_POINTER (-1)); + + token = get_token (invocation); + sender = g_strdup (request->sender + 1); + for (i = 0; sender[i]; i++) + if (sender[i] == '.') + sender[i] = '_'; + + id = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token); + + G_LOCK (requests); + + while (g_hash_table_lookup (requests, id) != NULL) + { + r = g_random_int (); + g_free (id); + id = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s/%u", sender, token, r); + } + + request->id = id; + g_hash_table_insert (requests, id, request); + + G_UNLOCK (requests); + + g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (request), + G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); + g_signal_connect (request, "g-authorize-method", + G_CALLBACK (request_authorize_callback), + request->sender); + + + g_object_set_data_full (G_OBJECT (invocation), "request", request, g_object_unref); +} + +XdpRequest * +xdp_request_from_invocation (GDBusMethodInvocation *invocation) +{ + return g_object_get_data (G_OBJECT (invocation), "request"); +} + +const char * +xdp_request_get_object_path (XdpRequest *request) +{ + return request->id; +} + +void +xdp_request_export (XdpRequest *request, + GDBusConnection *connection) +{ + g_autoptr(GError) error = NULL; + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (request), + connection, + request->id, + &error)) + { + g_warning ("Error exporting request: %s", error->message); + g_clear_error (&error); + } + + g_object_ref (request); + request->exported = TRUE; +} + +void +xdp_request_unexport (XdpRequest *request) +{ + int fd; + + fd = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "fd")); + if (fd != -1) + close (fd); + + request->exported = FALSE; + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (request)); + g_object_unref (request); +} + +void +xdp_request_set_impl_request (XdpRequest *request, + XdpDbusImplRequest *impl_request) +{ + g_set_object (&request->impl_request, impl_request); +} + +static void +xdp_close_requests_in_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + const char *sender = (const char *)task_data; + GSList *list = NULL; + GSList *l; + GHashTableIter iter; + XdpRequest *request; + + G_LOCK (requests); + if (requests) + { + g_hash_table_iter_init (&iter, requests); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&request)) + { + if (strcmp (sender, request->sender) == 0) + list = g_slist_prepend (list, g_object_ref (request)); + } + } + G_UNLOCK (requests); + + for (l = list; l; l = l->next) + { + XdpRequest *request = l->data; + + REQUEST_AUTOLOCK (request); + + if (request->exported) + { + if (request->impl_request) + xdp_dbus_impl_request_call_close_sync (request->impl_request, NULL, NULL); + + xdp_request_unexport (request); + } + } + + g_slist_free_full (list, g_object_unref); + g_task_return_boolean (task, TRUE); +} + +void +close_requests_for_sender (const char *sender) +{ + GTask *task; + + task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (task, g_strdup (sender), g_free); + g_task_run_in_thread (task, xdp_close_requests_in_thread_func); + g_object_unref (task); +} + diff --git a/src/request.h b/src/xdp-request.h similarity index 58% rename from src/request.h rename to src/xdp-request.h index 515a7d7..fe8fa9e 100644 --- a/src/request.h +++ b/src/xdp-request.h @@ -1,10 +1,12 @@ /* * Copyright © 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -21,6 +23,7 @@ #pragma once +#include "xdp-app-info.h" #include "xdp-utils.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" @@ -31,10 +34,7 @@ typedef enum { XDG_DESKTOP_PORTAL_RESPONSE_OTHER } XdgDesktopPortalResponseEnum; -typedef struct _Request Request; -typedef struct _RequestClass RequestClass; - -struct _Request +typedef struct _XdpRequest { XdpDbusRequestSkeleton parent_instance; @@ -45,28 +45,45 @@ struct _Request XdpAppInfo *app_info; XdpDbusImplRequest *impl_request; -}; +} XdpRequest; -struct _RequestClass +typedef struct _XdpRequestClass { XdpDbusRequestSkeletonClass parent_class; -}; +} XdpRequestClass; -GType request_get_type (void) G_GNUC_CONST; +GType xdp_request_get_type (void) G_GNUC_CONST; -G_DEFINE_AUTOPTR_CLEANUP_FUNC (Request, g_object_unref) +G_GNUC_UNUSED static inline XdpRequest * +XDP_REQUEST (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_CAST (ptr, xdp_request_get_type (), XdpRequest); +} -void request_init_invocation (GDBusMethodInvocation *invocation, XdpAppInfo *app_info); -Request *request_from_invocation (GDBusMethodInvocation *invocation); -void request_export (Request *request, - GDBusConnection *connection); -void request_unexport (Request *request); -void close_requests_for_sender (const char *sender); +G_GNUC_UNUSED static inline gboolean +XDP_IS_REQUEST (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_TYPE (ptr, xdp_request_get_type ()); +} -G_DEFINE_AUTOPTR_CLEANUP_FUNC (XdpDbusImplRequest, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (XdpRequest, g_object_unref) + +void xdp_request_init_invocation (GDBusMethodInvocation *invocation, + XdpAppInfo *app_info); + +XdpRequest *xdp_request_from_invocation (GDBusMethodInvocation *invocation); + +void xdp_request_export (XdpRequest *request, + GDBusConnection *connection); + +void xdp_request_unexport (XdpRequest *request); + +const char *xdp_request_get_object_path (XdpRequest *request); + +void close_requests_for_sender (const char *sender); -void request_set_impl_request (Request *request, - XdpDbusImplRequest *impl_request); +void xdp_request_set_impl_request (XdpRequest *request, + XdpDbusImplRequest *impl_request); static inline void auto_unlock_helper (GMutex **mutex) diff --git a/src/xdp-sealed-fd.c b/src/xdp-sealed-fd.c new file mode 100644 index 0000000..d285b2a --- /dev/null +++ b/src/xdp-sealed-fd.c @@ -0,0 +1,279 @@ +/* + * Copyright © 2024 GNOME Foundation Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Julian Sparber + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "xdp-utils.h" +#include "xdp-sealed-fd.h" + +#define REQUIRED_SEALS (F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SHRINK) + +struct _XdpSealedFd +{ + GObject parent_instance; + + int fd; +}; + +G_DEFINE_FINAL_TYPE (XdpSealedFd, xdp_sealed_fd, G_TYPE_OBJECT) + +static void +xdp_sealed_fd_finalize (GObject *object) +{ + XdpSealedFd *sealed_fd = XDP_SEALED_FD (object); + g_autoptr(GError) error = NULL; + + if (!g_clear_fd (&sealed_fd->fd, &error)) + g_warning ("Error closing sealed fd: %s", error->message); + + G_OBJECT_CLASS (xdp_sealed_fd_parent_class)->finalize (object); +} + +static void +xdp_sealed_fd_class_init (XdpSealedFdClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = xdp_sealed_fd_finalize; +} + +static void +xdp_sealed_fd_init (XdpSealedFd *sealed_fd) +{ + sealed_fd->fd = -1; +} + +XdpSealedFd * +xdp_sealed_fd_new_take_memfd (int memfd, + GError **error) +{ + g_autoptr(XdpSealedFd) sealed_fd = NULL; + g_autofd int fd = g_steal_fd (&memfd); + int saved_errno = -1; + int seals; + + g_return_val_if_fail (fd != -1, NULL); + + seals = fcntl (fd, F_GET_SEALS); + if (seals == -1) + { + saved_errno = errno; + + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (saved_errno), + "fcntl F_GET_SEALS: %s", g_strerror (saved_errno)); + return NULL; + } + + /* If the seal seal is set and some required seal is missing report EPERM error directly */ + if ((seals & F_SEAL_SEAL) && (seals & REQUIRED_SEALS) != REQUIRED_SEALS) + saved_errno = EPERM; + else if (fcntl (fd, F_ADD_SEALS, REQUIRED_SEALS) == -1) + saved_errno = errno; + + if (saved_errno != -1) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (saved_errno), + "fcntl F_ADD_SEALS: %s", g_strerror (saved_errno)); + return NULL; + } + + sealed_fd = g_object_new (XDP_TYPE_SEALED_FD, NULL); + sealed_fd->fd = g_steal_fd (&fd); + + return g_steal_pointer (&sealed_fd); +} + +XdpSealedFd * +xdp_sealed_fd_new_from_bytes (GBytes *bytes, + GError **error) +{ + g_autoptr(GOutputStream) stream = NULL; + g_autoptr(XdpSealedFd) sealed_fd = NULL; + g_autofd int fd = -1; + gconstpointer bytes_data; + gpointer shm; + gsize bytes_len; + int saved_errno = -1; + + g_return_val_if_fail (bytes != NULL, NULL); + + fd = memfd_create ("xdp-sealed-fd", MFD_ALLOW_SEALING); + if (fd == -1) + { + int saved_errno; + + saved_errno = errno; + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (saved_errno), + "memfd_create: %s", g_strerror (saved_errno)); + return NULL; + } + + bytes_data = g_bytes_get_data (bytes, &bytes_len); + + if (ftruncate (fd, bytes_len) == -1) + { + int saved_errno; + + saved_errno = errno; + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (saved_errno), + "ftruncate: %s", g_strerror (saved_errno)); + return NULL; + } + + shm = mmap (NULL, bytes_len, PROT_WRITE, MAP_SHARED, fd, 0); + if (shm == MAP_FAILED) + { + int saved_errno; + + saved_errno = errno; + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (saved_errno), + "mmap: %s", g_strerror (saved_errno)); + return NULL; + } + + memcpy (shm, bytes_data, bytes_len); + + if (munmap (shm, bytes_len) == -1) + { + int saved_errno; + + saved_errno = errno; + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (saved_errno), + "munmap: %s", g_strerror (saved_errno)); + return NULL; + } + + if (fcntl (fd, F_ADD_SEALS, REQUIRED_SEALS) == -1) + { + saved_errno = errno; + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (saved_errno), + "fcntl F_ADD_SEALS: %s", g_strerror (saved_errno)); + return NULL; + } + + sealed_fd = g_object_new (XDP_TYPE_SEALED_FD, NULL); + sealed_fd->fd = g_steal_fd (&fd); + + return g_steal_pointer (&sealed_fd); +} + +XdpSealedFd * +xdp_sealed_fd_new_from_handle (GVariant *handle, + GUnixFDList *fd_list, + GError **error) +{ + g_autofd int fd = -1; + int fd_id; + + if (!g_variant_is_of_type (handle, G_VARIANT_TYPE_HANDLE)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "GVariant is not a file descriptor handle"); + return NULL; + } + + if (!fd_list) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid file descriptor: index not found (empty list)"); + return NULL; + } + + fd_id = g_variant_get_handle (handle); + if (fd_id >= g_unix_fd_list_get_length (fd_list)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid file descriptor: index not found"); + return NULL; + } + + fd = g_unix_fd_list_get (fd_list, fd_id, error); + if (fd == -1) + return NULL; + + return xdp_sealed_fd_new_take_memfd (g_steal_fd (&fd), error); +} + +int +xdp_sealed_fd_get_fd (XdpSealedFd *sealed_fd) +{ + g_return_val_if_fail (XDP_IS_SEALED_FD (sealed_fd), -1); + + return sealed_fd->fd; +} + +int +xdp_sealed_fd_dup_fd (XdpSealedFd *sealed_fd) +{ + g_return_val_if_fail (XDP_IS_SEALED_FD (sealed_fd), -1); + + return dup (sealed_fd->fd); +} + +GBytes * +xdp_sealed_fd_get_bytes (XdpSealedFd *sealed_fd, + GError **error) +{ + g_autoptr(GMappedFile) mapped = NULL; + + mapped = g_mapped_file_new_from_fd (sealed_fd->fd, FALSE, error); + return g_mapped_file_get_bytes (mapped); +} + +GVariant * +xdp_sealed_fd_to_handle (XdpSealedFd *sealed_fd, + GUnixFDList *fd_list, + GError **error) +{ + int fd_out; + + g_return_val_if_fail (XDP_IS_SEALED_FD (sealed_fd), NULL); + g_return_val_if_fail (G_IS_UNIX_FD_LIST (fd_list), NULL); + + fd_out = g_unix_fd_list_append (fd_list, sealed_fd->fd, error); + + if (fd_out == -1) + return NULL; + + return g_variant_ref_sink (g_variant_new ("(sv)", "file-descriptor", g_variant_new_handle (fd_out))); +} diff --git a/src/xdp-sealed-fd.h b/src/xdp-sealed-fd.h new file mode 100644 index 0000000..30d65b1 --- /dev/null +++ b/src/xdp-sealed-fd.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2024 GNOME Foundation Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include + +#define XDP_TYPE_SEALED_FD (xdp_sealed_fd_get_type()) +G_DECLARE_FINAL_TYPE (XdpSealedFd, + xdp_sealed_fd, + XDP, SEALED_FD, + GObject) + +XdpSealedFd * xdp_sealed_fd_new_take_memfd (int memfd, + GError **error); +XdpSealedFd * xdp_sealed_fd_new_from_bytes (GBytes *bytes, + GError **error); +XdpSealedFd * xdp_sealed_fd_new_from_handle (GVariant *handle, + GUnixFDList *fd_list, + GError **error); +int xdp_sealed_fd_get_fd (XdpSealedFd *sealed_fd); +int xdp_sealed_fd_dup_fd (XdpSealedFd *sealed_fd); + +GBytes *xdp_sealed_fd_get_bytes (XdpSealedFd *sealed_fd, + GError **error); +GVariant *xdp_sealed_fd_to_handle (XdpSealedFd *sealed_fd, + GUnixFDList *fd_list, + GError **error); diff --git a/src/restore-token.c b/src/xdp-session-persistence.c similarity index 86% rename from src/restore-token.c rename to src/xdp-session-persistence.c index cbe26c8..bc9faa7 100644 --- a/src/restore-token.c +++ b/src/xdp-session-persistence.c @@ -2,10 +2,12 @@ * Copyright 2021-2022 Endless OS Foundation, LLC * Copyright 2023 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -19,8 +21,8 @@ #include "config.h" -#include "permissions.h" -#include "restore-token.h" +#include "xdp-permissions.h" +#include "xdp-session-persistence.h" static GMutex transient_permissions_lock; static GHashTable *transient_permissions; @@ -28,7 +30,7 @@ static GHashTable *transient_permissions; #define RESTORE_DATA_TYPE "(suv)" void -xdp_session_persistence_set_transient_permissions (Session *session, +xdp_session_persistence_set_transient_permissions (XdpSession *session, const char *restore_token, GVariant *restore_data) { @@ -47,7 +49,7 @@ xdp_session_persistence_set_transient_permissions (Session *session, } void -xdp_session_persistence_delete_transient_permissions (Session *session, +xdp_session_persistence_delete_transient_permissions (XdpSession *session, const char *restore_token) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&transient_permissions_lock); @@ -84,7 +86,7 @@ xdp_session_persistence_delete_transient_permissions_for_sender (const char *sen } GVariant * -xdp_session_persistence_get_transient_permissions (Session *session, +xdp_session_persistence_get_transient_permissions (XdpSession *session, const char *restore_token) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&transient_permissions_lock); @@ -100,21 +102,21 @@ xdp_session_persistence_get_transient_permissions (Session *session, } void -xdp_session_persistence_set_persistent_permissions (Session *session, +xdp_session_persistence_set_persistent_permissions (XdpSession *session, const char *table, const char *restore_token, GVariant *restore_data) { g_autoptr(GError) error = NULL; - GVariantBuilder permissions_builder; + g_auto(GVariantBuilder) permissions_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{sas}")); g_auto(GStrv) permission = NULL; - permission = permissions_from_tristate (PERMISSION_YES); + permission = xdp_permissions_from_tristate (XDP_PERMISSION_YES); - g_variant_builder_init (&permissions_builder, G_VARIANT_TYPE ("a{sas}")); g_variant_builder_add (&permissions_builder, "{s^a&s}", session->app_id, permission); - if (!xdp_dbus_impl_permission_store_call_set_sync (get_permission_store (), + if (!xdp_dbus_impl_permission_store_call_set_sync (xdp_get_permission_store (), table, TRUE, restore_token, @@ -129,14 +131,14 @@ xdp_session_persistence_set_persistent_permissions (Session *session, } void -xdp_session_persistence_delete_persistent_permissions (Session *session, +xdp_session_persistence_delete_persistent_permissions (XdpSession *session, const char *table, const char *restore_token) { g_autoptr(GError) error = NULL; - if (!xdp_dbus_impl_permission_store_call_delete_sync (get_permission_store (), + if (!xdp_dbus_impl_permission_store_call_delete_sync (xdp_get_permission_store (), table, restore_token, NULL, @@ -148,7 +150,7 @@ xdp_session_persistence_delete_persistent_permissions (Session *session, } GVariant * -xdp_session_persistence_get_persistent_permissions (Session *session, +xdp_session_persistence_get_persistent_permissions (XdpSession *session, const char *table, const char *restore_token) { @@ -157,7 +159,7 @@ xdp_session_persistence_get_persistent_permissions (Session *session, g_autoptr(GError) error = NULL; const char **permissions; - if (!xdp_dbus_impl_permission_store_call_lookup_sync (get_permission_store (), + if (!xdp_dbus_impl_permission_store_call_lookup_sync (xdp_get_permission_store (), table, restore_token, &perms, @@ -178,20 +180,19 @@ xdp_session_persistence_get_persistent_permissions (Session *session, } void -xdp_session_persistence_replace_restore_token_with_data (Session *session, +xdp_session_persistence_replace_restore_token_with_data (XdpSession *session, const char *table, GVariant **in_out_options, char **out_restore_token) { GVariantIter options_iter; - GVariantBuilder options_builder; + g_auto(GVariantBuilder) options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); char *key; GVariant *value; g_variant_iter_init (&options_iter, *in_out_options); - g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); - while (g_variant_iter_next (&options_iter, "{&sv}", &key, &value)) { if (g_strcmp0 (key, "restore_token") == 0) @@ -248,13 +249,13 @@ xdp_session_persistence_replace_restore_token_with_data (Session *session, g_clear_pointer (&value, g_variant_unref); } - *in_out_options = g_variant_builder_end (&options_builder); + *in_out_options = g_variant_ref_sink (g_variant_builder_end (&options_builder)); } void -xdp_session_persistence_generate_and_save_restore_token (Session *session, +xdp_session_persistence_generate_and_save_restore_token (XdpSession *session, const char *table, - PersistMode persist_mode, + XdpSessionPersistenceMode persist_mode, char **in_out_restore_token, GVariant **in_out_restore_data) { @@ -275,7 +276,7 @@ xdp_session_persistence_generate_and_save_restore_token (Session *session, switch (persist_mode) { - case PERSIST_MODE_NONE: + case XDP_SESSION_PERSISTENCE_MODE_NONE: if (*in_out_restore_token) { xdp_session_persistence_delete_persistent_permissions (session, @@ -289,7 +290,7 @@ xdp_session_persistence_generate_and_save_restore_token (Session *session, g_clear_pointer (in_out_restore_data, g_variant_unref); break; - case PERSIST_MODE_TRANSIENT: + case XDP_SESSION_PERSISTENCE_MODE_TRANSIENT: if (!*in_out_restore_token) *in_out_restore_token = g_uuid_string_random (); @@ -298,7 +299,7 @@ xdp_session_persistence_generate_and_save_restore_token (Session *session, *in_out_restore_data); break; - case PERSIST_MODE_PERSISTENT: + case XDP_SESSION_PERSISTENCE_MODE_PERSISTENT: if (!*in_out_restore_token) *in_out_restore_token = g_uuid_string_random (); @@ -312,22 +313,21 @@ xdp_session_persistence_generate_and_save_restore_token (Session *session, } void -xdp_session_persistence_replace_restore_data_with_token (Session *session, +xdp_session_persistence_replace_restore_data_with_token (XdpSession *session, const char *table, GVariant **in_out_results, - PersistMode *in_out_persist_mode, + XdpSessionPersistenceMode *in_out_persist_mode, char **in_out_restore_token, GVariant **in_out_restore_data) { g_autoptr(GVariant) results = *in_out_results; - GVariantBuilder results_builder; + g_auto(GVariantBuilder) results_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); GVariantIter iter; const char *key; GVariant *value; gboolean found_restore_data = FALSE; - g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); - g_variant_iter_init (&iter, results); while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) { @@ -371,8 +371,8 @@ xdp_session_persistence_replace_restore_data_with_token (Session *session, } else { - *in_out_persist_mode = PERSIST_MODE_NONE; + *in_out_persist_mode = XDP_SESSION_PERSISTENCE_MODE_NONE; } - *in_out_results = g_variant_builder_end (&results_builder); + *in_out_results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); } diff --git a/src/restore-token.h b/src/xdp-session-persistence.h similarity index 68% rename from src/restore-token.h rename to src/xdp-session-persistence.h index fdd3389..2ee3530 100644 --- a/src/restore-token.h +++ b/src/xdp-session-persistence.h @@ -1,10 +1,12 @@ /* * Copyright © 2023 Red Hat * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -18,54 +20,54 @@ #pragma once -#include "session.h" +#include "xdp-session.h" -typedef enum _PersistMode +typedef enum _XdpSessionPersistenceMode { - PERSIST_MODE_NONE = 0, - PERSIST_MODE_TRANSIENT = 1, - PERSIST_MODE_PERSISTENT = 2, -} PersistMode; + XDP_SESSION_PERSISTENCE_MODE_NONE = 0, + XDP_SESSION_PERSISTENCE_MODE_TRANSIENT = 1, + XDP_SESSION_PERSISTENCE_MODE_PERSISTENT = 2, +} XdpSessionPersistenceMode; -void xdp_session_persistence_set_transient_permissions (Session *session, +void xdp_session_persistence_set_transient_permissions (XdpSession *session, const char *restore_token, GVariant *restore_data); -void xdp_session_persistence_delete_transient_permissions (Session *session, +void xdp_session_persistence_delete_transient_permissions (XdpSession *session, const char *restore_token); void xdp_session_persistence_delete_transient_permissions_for_sender (const char *sender_name); -GVariant * xdp_session_persistence_get_transient_permissions (Session *session, +GVariant * xdp_session_persistence_get_transient_permissions (XdpSession *session, const char *restore_token); -void xdp_session_persistence_set_persistent_permissions (Session *session, +void xdp_session_persistence_set_persistent_permissions (XdpSession *session, const char *table, const char *restore_token, GVariant *restore_data); -void xdp_session_persistence_delete_persistent_permissions (Session *session, +void xdp_session_persistence_delete_persistent_permissions (XdpSession *session, const char *table, const char *restore_token); -GVariant * xdp_session_persistence_get_persistent_permissions (Session *session, +GVariant * xdp_session_persistence_get_persistent_permissions (XdpSession *session, const char *table, const char *restore_token); -void xdp_session_persistence_replace_restore_token_with_data (Session *session, +void xdp_session_persistence_replace_restore_token_with_data (XdpSession *session, const char *table, GVariant **in_out_options, char **out_restore_token); -void xdp_session_persistence_replace_restore_data_with_token (Session *session, +void xdp_session_persistence_replace_restore_data_with_token (XdpSession *session, const char *table, GVariant **in_out_results, - PersistMode *in_out_persist_mode, + XdpSessionPersistenceMode *in_out_persist_mode, char **in_out_restore_token, GVariant **in_out_restore_data); -void xdp_session_persistence_generate_and_save_restore_token (Session *session, +void xdp_session_persistence_generate_and_save_restore_token (XdpSession *session, const char *table, - PersistMode persist_mode, + XdpSessionPersistenceMode persist_mode, char **in_out_restore_token, GVariant **in_out_restore_data); diff --git a/src/session.c b/src/xdp-session.c similarity index 75% rename from src/session.c rename to src/xdp-session.c index 2c9e1c7..e9ae437 100644 --- a/src/session.c +++ b/src/xdp-session.c @@ -1,10 +1,12 @@ /* * Copyright © 2017 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,9 +18,9 @@ * */ -#include "session.h" -#include "request.h" -#include "call.h" +#include "xdp-session.h" +#include "xdp-request.h" +#include "xdp-call.h" #include @@ -42,16 +44,16 @@ G_LOCK_DEFINE (sessions); static GHashTable *sessions; static void g_initable_iface_init (GInitableIface *iface); -static void session_skeleton_iface_init (XdpDbusSessionIface *iface); +static void xdp_session_skeleton_iface_init (XdpDbusSessionIface *iface); -G_DEFINE_TYPE_WITH_CODE (Session, session, XDP_DBUS_TYPE_SESSION_SKELETON, +G_DEFINE_TYPE_WITH_CODE (XdpSession, xdp_session, XDP_DBUS_TYPE_SESSION_SKELETON, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, g_initable_iface_init) G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_SESSION, - session_skeleton_iface_init)) + xdp_session_skeleton_iface_init)) -#define SESSION_GET_CLASS(o) \ - (G_TYPE_INSTANCE_GET_CLASS ((o), session_get_type (), SessionClass)) +#define XDP_SESSION_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), xdp_session_get_type (), XdpSessionClass)) const char * lookup_session_token (GVariant *options) @@ -63,11 +65,11 @@ lookup_session_token (GVariant *options) return token; } -Session * -acquire_session (const char *session_handle, - Request *request) +XdpSession * +xdp_session_from_request (const char *session_handle, + XdpRequest *request) { - g_autoptr(Session) session = NULL; + g_autoptr(XdpSession) session = NULL; G_LOCK (sessions); session = g_hash_table_lookup (sessions, session_handle); @@ -87,11 +89,11 @@ acquire_session (const char *session_handle, return g_steal_pointer (&session); } -Session * -acquire_session_from_call (const char *session_handle, - Call *call) +XdpSession * +xdp_session_from_call (const char *session_handle, + XdpCall *call) { - g_autoptr(Session) session = NULL; + g_autoptr(XdpSession) session = NULL; G_LOCK (sessions); session = g_hash_table_lookup (sessions, session_handle); @@ -111,10 +113,10 @@ acquire_session_from_call (const char *session_handle, return g_steal_pointer (&session); } -Session * -lookup_session (const char *session_handle) +XdpSession * +xdp_session_lookup (const char *session_handle) { - g_autoptr(Session) session = NULL; + g_autoptr(XdpSession) session = NULL; G_LOCK (sessions); session = g_hash_table_lookup (sessions, session_handle); @@ -126,8 +128,8 @@ lookup_session (const char *session_handle) } gboolean -session_export (Session *session, - GError **error) +xdp_session_export (XdpSession *session, + GError **error) { if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (session), session->connection, @@ -142,7 +144,7 @@ session_export (Session *session, } static void -session_unexport (Session *session) +xdp_session_unexport (XdpSession *session) { session->exported = FALSE; g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (session)); @@ -150,7 +152,7 @@ session_unexport (Session *session) } void -session_register (Session *session) +xdp_session_register (XdpSession *session) { G_LOCK (sessions); g_hash_table_insert (sessions, session->id, session); @@ -158,7 +160,7 @@ session_register (Session *session) } static void -session_unregister (Session *session) +xdp_session_unregister (XdpSession *session) { G_LOCK (sessions); g_hash_table_remove (sessions, session->id); @@ -166,19 +168,19 @@ session_unregister (Session *session) } void -session_close (Session *session, - gboolean notify_closed) +xdp_session_close (XdpSession *session, + gboolean notify_closed) { if (session->closed) return; - SESSION_GET_CLASS (session)->close (session); + XDP_SESSION_GET_CLASS (session)->close (session); if (notify_closed) { - GVariantBuilder details_builder; + g_auto(GVariantBuilder) details_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&details_builder, G_VARIANT_TYPE_VARDICT); g_dbus_connection_emit_signal (session->connection, session->sender, session->id, @@ -189,9 +191,9 @@ session_close (Session *session, } if (session->exported) - session_unexport (session); + xdp_session_unexport (session); - session_unregister (session); + xdp_session_unregister (session); if (session->impl_session) { @@ -215,11 +217,11 @@ static gboolean handle_close (XdpDbusSession *object, GDBusMethodInvocation *invocation) { - Session *session = (Session *)object; + XdpSession *session = XDP_SESSION (object); SESSION_AUTOLOCK_UNREF (g_object_ref (session)); - session_close (session, FALSE); + xdp_session_close (session, FALSE); xdp_dbus_session_complete_close (object, invocation); @@ -227,7 +229,7 @@ handle_close (XdpDbusSession *object, } static void -session_skeleton_iface_init (XdpDbusSessionIface *iface) +xdp_session_skeleton_iface_init (XdpDbusSessionIface *iface) { iface->handle_close = handle_close; } @@ -242,7 +244,7 @@ close_sessions_in_thread_func (GTask *task, GSList *list = NULL; GSList *l; GHashTableIter iter; - Session *session; + XdpSession *session; G_LOCK (sessions); if (sessions) @@ -258,10 +260,10 @@ close_sessions_in_thread_func (GTask *task, for (l = list; l; l = l->next) { - Session *session = l->data; + XdpSession *session = l->data; SESSION_AUTOLOCK (session); - session_close (session, FALSE); + xdp_session_close (session, FALSE); } g_slist_free_full (list, g_object_unref); @@ -281,18 +283,18 @@ close_sessions_for_sender (const char *sender) static void on_closed (XdpDbusImplSession *object, GObject *data) { - Session *session = (Session *)data; + XdpSession *session = XDP_SESSION (data); SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_clear_object (&session->impl_session); - session_close (session, TRUE); + xdp_session_close (session, TRUE); } static gboolean -session_authorize_callback (GDBusInterfaceSkeleton *interface, - GDBusMethodInvocation *invocation, - gpointer user_data) +xdp_session_authorize_callback (GDBusInterfaceSkeleton *interface, + GDBusMethodInvocation *invocation, + gpointer user_data) { const gchar *session_owner = user_data; const gchar *sender = g_dbus_method_invocation_get_sender (invocation); @@ -310,11 +312,28 @@ session_authorize_callback (GDBusInterfaceSkeleton *interface, } static gboolean -session_initable_init (GInitable *initable, - GCancellable *cancellable, - GError **error) +is_valid_token (const char *token) +{ + g_autofree char *path = NULL; + int i; + + for (i = 0; token[i]; i++) + { + if (token[i] == '/') + return FALSE; + } + + path = g_strdup_printf ("/foo/%s", token); + + return g_variant_is_object_path (path); +} + +static gboolean +xdp_session_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) { - Session *session = (Session *)initable; + XdpSession *session = XDP_SESSION (initable); g_autofree char *sender_escaped = NULL; g_autofree char *id = NULL; g_autoptr(XdpDbusImplSession) impl_session = NULL; @@ -333,6 +352,15 @@ session_initable_init (GInitable *initable, return FALSE; } + if (!is_valid_token (session->token)) + { + g_set_error (error, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Invalid token '%s'", session->token); + return FALSE; + } + id = g_strdup_printf ("/org/freedesktop/portal/desktop/session/%s/%s", sender_escaped, session->token); @@ -357,7 +385,7 @@ session_initable_init (GInitable *initable, g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (session), G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); g_signal_connect (session, "g-authorize-method", - G_CALLBACK (session_authorize_callback), + G_CALLBACK (xdp_session_authorize_callback), session->sender); return TRUE; @@ -366,16 +394,16 @@ session_initable_init (GInitable *initable, static void g_initable_iface_init (GInitableIface *iface) { - iface->init = session_initable_init; + iface->init = xdp_session_initable_init; } static void -session_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) +xdp_session_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { - Session *session = (Session *)object; + XdpSession *session = XDP_SESSION (object); switch (prop_id) { @@ -409,12 +437,12 @@ session_set_property (GObject *object, } static void -session_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) +xdp_session_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - Session *session = (Session *)object; + XdpSession *session = XDP_SESSION (object); switch (prop_id) { @@ -448,9 +476,9 @@ session_get_property (GObject *object, } static void -session_finalize (GObject *object) +xdp_session_finalize (GObject *object) { - Session *session = (Session *)object; + XdpSession *session = XDP_SESSION (object); g_assert (!session->id || !g_hash_table_lookup (sessions, session->id)); @@ -467,17 +495,17 @@ session_finalize (GObject *object) g_mutex_clear (&session->mutex); - G_OBJECT_CLASS (session_parent_class)->finalize (object); + G_OBJECT_CLASS (xdp_session_parent_class)->finalize (object); } static void -session_init (Session *session) +xdp_session_init (XdpSession *session) { g_mutex_init (&session->mutex); } static void -session_class_init (SessionClass *klass) +xdp_session_class_init (XdpSessionClass *klass) { GObjectClass *gobject_class; @@ -485,9 +513,9 @@ session_class_init (SessionClass *klass) NULL, NULL); gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = session_finalize; - gobject_class->set_property = session_set_property; - gobject_class->get_property = session_get_property; + gobject_class->finalize = xdp_session_finalize; + gobject_class->set_property = xdp_session_set_property; + gobject_class->get_property = xdp_session_get_property; obj_props[PROP_SENDER] = g_param_spec_string ("sender", "Sender", "Sender", diff --git a/src/session.h b/src/xdp-session.h similarity index 54% rename from src/session.h rename to src/xdp-session.h index 14e6204..507e8f9 100644 --- a/src/session.h +++ b/src/xdp-session.h @@ -1,10 +1,12 @@ /* * Copyright © 2017 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -18,15 +20,12 @@ #pragma once -#include "request.h" -#include "call.h" +#include "xdp-request.h" +#include "xdp-call.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" -typedef struct _Session Session; -typedef struct _SessionClass SessionClass; - -struct _Session +typedef struct _XdpSession { XdpDbusSessionSkeleton parent; @@ -45,42 +44,53 @@ struct _Session char *impl_dbus_name; GDBusConnection *impl_connection; XdpDbusImplSession *impl_session; -}; +} XdpSession; -struct _SessionClass +typedef struct _XdpSessionClass { XdpDbusSessionSkeletonClass parent_class; - void (*close) (Session *session); -}; + void (*close) (XdpSession *session); +} XdpSessionClass; + +GType xdp_session_get_type (void); + +G_GNUC_UNUSED static inline XdpSession * +XDP_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_CAST (ptr, xdp_session_get_type (), XdpSession); +} -GType session_get_type (void); +G_GNUC_UNUSED static inline gboolean +XDP_IS_SESSION (gpointer ptr) +{ + return G_TYPE_CHECK_INSTANCE_TYPE (ptr, xdp_session_get_type ()); +} -G_DEFINE_AUTOPTR_CLEANUP_FUNC (Session, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (XdpDbusImplSession, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (XdpSession, g_object_unref) const char * lookup_session_token (GVariant *options); -Session * acquire_session (const char *session_handle, - Request *request); +XdpSession * xdp_session_from_request (const char *session_handle, + XdpRequest *request); -Session * acquire_session_from_call (const char *session_handle, - Call *call); +XdpSession * xdp_session_from_call (const char *session_handle, + XdpCall *call); -Session * lookup_session (const char *session_handle); +XdpSession * xdp_session_lookup (const char *session_handle); -void session_register (Session *session); +void xdp_session_register (XdpSession *session); -gboolean session_export (Session *session, - GError **error); +gboolean xdp_session_export (XdpSession *session, + GError **error); void close_sessions_for_sender (const char *sender); -void session_close (Session *session, - gboolean notify_close); +void xdp_session_close (XdpSession *session, + gboolean notify_close); static inline void -auto_session_unlock_unref_helper (Session **session) +auto_session_unlock_unref_helper (XdpSession **session) { if (!*session) return; @@ -89,8 +99,8 @@ auto_session_unlock_unref_helper (Session **session) g_object_unref (*session); } -static inline Session * -auto_session_lock_helper (Session *session) +static inline XdpSession * +auto_session_lock_helper (XdpSession *session) { if (session) g_mutex_lock (&session->mutex); @@ -104,5 +114,5 @@ auto_session_lock_helper (Session *session) #define SESSION_AUTOLOCK_UNREF(session) \ G_GNUC_UNUSED __attribute__((cleanup (auto_session_unlock_unref_helper))) \ - Session * G_PASTE (session_auto_unlock_unref, __LINE__) = \ + XdpSession * G_PASTE (session_auto_unlock_unref, __LINE__) = \ auto_session_lock_helper (session); diff --git a/src/xdp-usb-query.c b/src/xdp-usb-query.c new file mode 100644 index 0000000..4f2dba4 --- /dev/null +++ b/src/xdp-usb-query.c @@ -0,0 +1,213 @@ +/* + * Copyright © 2023-2024 GNOME Foundation Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Georges Basile Stavracas Neto + * Hubert Figuière + */ + +#include +#include + +#include "xdp-usb-query.h" + +static void +xdp_usb_rule_free (XdpUsbRule *rule) +{ + g_free (rule); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (XdpUsbRule, xdp_usb_rule_free); + +gboolean +xdp_validate_hex_uint16 (const char *value, + size_t expected_length, + uint16_t *out_value) +{ + size_t len; + char *end; + long n; + + g_assert (value != NULL); + g_assert (expected_length > 0 && expected_length <= 4); + + len = strlen (value); + if (len != expected_length) + return FALSE; + + n = strtol (value, &end, 16); + + if (end - value != len) + return FALSE; + + if (n <= 0 || n > UINT16_MAX) + return FALSE; + + if (out_value) + *out_value = n; + + return TRUE; +} + +static gboolean +parse_all_usb_rule (XdpUsbRule *dest, + GStrv data) +{ + if (g_strv_length (data) != 1) + return FALSE; + + dest->rule_type = XDP_USB_RULE_TYPE_ALL; + return TRUE; +} + +static gboolean +parse_cls_usb_rule (XdpUsbRule *dest, + GStrv data) +{ + const char *subclass; + const char *class; + + if (g_strv_length (data) < 3) + return FALSE; + + class = data[1]; + subclass = data[2]; + + if (!xdp_validate_hex_uint16 (class, 2, &dest->d.device_class.class)) + return FALSE; + + if (g_strcmp0 (subclass, "*") == 0) + dest->d.device_class.type = XDP_USB_RULE_CLASS_TYPE_CLASS_ONLY; + else if (xdp_validate_hex_uint16 (subclass, 2, &dest->d.device_class.subclass)) + dest->d.device_class.type = XDP_USB_RULE_CLASS_TYPE_CLASS_SUBCLASS; + else + return FALSE; + + dest->rule_type = XDP_USB_RULE_TYPE_CLASS; + return TRUE; +} + +static gboolean +parse_dev_usb_rule (XdpUsbRule *dest, + GStrv data) +{ + if (g_strv_length (data) != 2) + return FALSE; + + if (!xdp_validate_hex_uint16 (data[1], 4, &dest->d.product.id)) + return FALSE; + + dest->rule_type = XDP_USB_RULE_TYPE_DEVICE; + return TRUE; +} + +static gboolean +parse_vnd_usb_rule (XdpUsbRule *dest, + GStrv data) +{ + if (g_strv_length (data) != 2) + return FALSE; + + if (!xdp_validate_hex_uint16 (data[1], 4, &dest->d.product.id)) + return FALSE; + + dest->rule_type = XDP_USB_RULE_TYPE_VENDOR; + return TRUE; +} + +static const struct { + const char *name; + gboolean (*parse) (XdpUsbRule *dest, + GStrv data); +} rule_parsers[] = { + { "all", parse_all_usb_rule }, + { "cls", parse_cls_usb_rule }, + { "dev", parse_dev_usb_rule }, + { "vnd", parse_vnd_usb_rule }, +}; + +static XdpUsbRule * +xdp_usb_rule_from_string (const char *string) +{ + g_autoptr(XdpUsbRule) usb_rule = NULL; + g_auto(GStrv) split = NULL; + gboolean parsed = FALSE; + + split = g_strsplit (string, ":", 0); + + if (!split || g_strv_length (split) > 3) + return NULL; + + usb_rule = g_new0 (XdpUsbRule, 1); + + for (size_t i = 0; i < G_N_ELEMENTS (rule_parsers); i++) + { + if (g_strcmp0 (rule_parsers[i].name, split[0]) == 0) + { + if (!rule_parsers[i].parse (usb_rule, split)) + return FALSE; + + parsed = TRUE; + break; + } + } + + if (!parsed) + return NULL; + + return g_steal_pointer (&usb_rule); +} + +void +xdp_usb_query_free (XdpUsbQuery *query) +{ + g_return_if_fail (query != NULL); + + g_clear_pointer (&query->rules, g_ptr_array_unref); + g_free (query); +} + +XdpUsbQuery * +xdp_usb_query_from_string (XdpUsbQueryType query_type, + const char *string) +{ + g_autoptr(XdpUsbQuery) usb_query = NULL; + g_auto(GStrv) split = NULL; + + split = g_strsplit (string, "+", 0); + if (!split) + return NULL; + + usb_query = g_new0 (XdpUsbQuery, 1); + usb_query->query_type = query_type; + usb_query->rules = g_ptr_array_new_with_free_func ((GDestroyNotify) xdp_usb_rule_free); + + for (size_t i = 0; split[i] != NULL; i++) + { + g_autoptr(XdpUsbRule) usb_rule = NULL; + const char *rule = split[i]; + + usb_rule = xdp_usb_rule_from_string (rule); + if (!usb_rule) + return NULL; + + g_ptr_array_add (usb_query->rules, g_steal_pointer (&usb_rule)); + } + + g_return_val_if_fail (usb_query->rules->len > 0, NULL); + + return g_steal_pointer (&usb_query); +} diff --git a/src/xdp-usb-query.h b/src/xdp-usb-query.h new file mode 100644 index 0000000..0643337 --- /dev/null +++ b/src/xdp-usb-query.h @@ -0,0 +1,89 @@ +/* + * Copyright © 2023-2024 GNOME Foundation Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Georges Basile Stavracas Neto + * Hubert Figuière + */ + +#include + +#pragma once + +typedef enum +{ + XDP_USB_RULE_TYPE_ALL, + XDP_USB_RULE_TYPE_CLASS, + XDP_USB_RULE_TYPE_DEVICE, + XDP_USB_RULE_TYPE_VENDOR, +} UsbRuleType; + +typedef enum +{ + XDP_USB_RULE_CLASS_TYPE_CLASS_ONLY, + XDP_USB_RULE_CLASS_TYPE_CLASS_SUBCLASS, +} UsbDeviceClassType; + +typedef struct +{ + UsbDeviceClassType type; + uint16_t class; + uint16_t subclass; +} UsbDeviceClass; + +typedef struct +{ + uint16_t id; +} UsbProduct; + +typedef struct +{ + uint16_t id; +} UsbVendor; + +typedef struct +{ + UsbRuleType rule_type; + + union { + UsbDeviceClass device_class; + UsbProduct product; + UsbVendor vendor; + } d; +} XdpUsbRule; + +typedef enum +{ + XDP_USB_QUERY_TYPE_HIDDEN, + XDP_USB_QUERY_TYPE_ENUMERABLE, +} XdpUsbQueryType; + +typedef struct +{ + XdpUsbQueryType query_type; + GPtrArray *rules; +} XdpUsbQuery; + +void xdp_usb_query_free (XdpUsbQuery *query); +XdpUsbQuery *xdp_usb_query_from_string (XdpUsbQueryType query_type, + const char *string); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (XdpUsbQuery, xdp_usb_query_free); + +gboolean +xdp_validate_hex_uint16 (const char *value, + size_t expected_length, + uint16_t *out_value); diff --git a/src/xdp-utils.c b/src/xdp-utils.c index eb59444..902bb31 100644 --- a/src/xdp-utils.c +++ b/src/xdp-utils.c @@ -1,10 +1,12 @@ /* * Copyright © 2014 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -22,28 +24,14 @@ #include -#include -#include -#include -#include -#include #include +#include +#include #include -#ifdef HAVE_SYS_VFS_H -#include -#endif -#ifdef HAVE_SYS_MOUNT_H -#include -#endif - -#ifdef HAVE_LIBSYSTEMD -#include -#include "sd-escape.h" -#endif +#include #include #include -#include #include "xdp-utils.h" @@ -51,11 +39,7 @@ #define DBUS_INTERFACE_DBUS DBUS_NAME_DBUS #define DBUS_PATH_DBUS "/org/freedesktop/DBus" -G_LOCK_DEFINE (app_infos); -static GHashTable *app_info_by_unique_name; - /* Based on g_mkstemp from glib */ - gint xdp_mkstempat (int dir_fd, gchar *tmpl, @@ -119,1836 +103,678 @@ xdp_mkstempat (int dir_fd, return -1; } -struct _XdpAppInfo { - volatile gint ref_count; - char *id; - XdpAppInfoKind kind; - - union - { - struct - { - GKeyFile *keyfile; - /* pid namespace mapping */ - GMutex pidns_lock; - ino_t pidns_id; - } flatpak; - struct - { - GKeyFile *keyfile; - } snap; - } u; -}; - -static XdpAppInfo * -xdp_app_info_new (XdpAppInfoKind kind) +static gboolean +needs_quoting (const char *arg) { - XdpAppInfo *app_info = g_new0 (XdpAppInfo, 1); - app_info->ref_count = 1; - app_info->kind = kind; - return app_info; + while (*arg != 0) + { + char c = *arg; + if (!g_ascii_isalnum (c) && + !(c == '-' || c == '/' || c == '~' || + c == ':' || c == '.' || c == '_' || + c == '=' || c == '@')) + return TRUE; + arg++; + } + return FALSE; } -#ifdef HAVE_LIBSYSTEMD -char * -_xdp_parse_app_id_from_unit_name (const char *unit) +static void +name_owner_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) { - g_autoptr(GRegex) regex1 = NULL; - g_autoptr(GRegex) regex2 = NULL; - g_autoptr(GMatchInfo) match = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *app_id = NULL; - - g_assert (g_str_has_prefix (unit, "app-")); - - /* - * From https://systemd.io/DESKTOP_ENVIRONMENTS/ the format is one of: - * app[-]--.scope - * app[-]--.slice - */ - regex1 = g_regex_new ("^app-(?:[[:alnum:]]+\\-)?(.+?)(?:\\-[[:alnum:]]*)(?:\\.scope|\\.slice)$", 0, 0, &error); - g_assert (error == NULL); - /* - * app[-]--autostart.service -> no longer true since systemd v248 - * app[-]-[@].service - */ - regex2 = g_regex_new ("^app-(?:[[:alnum:]]+\\-)?(.+?)(?:@[[:alnum:]]*|\\-autostart)?\\.service$", 0, 0, &error); - g_assert (error == NULL); + const char *name, *from, *to; + XdpPeerDiedCallback peer_died_cb = user_data; - if (!g_regex_match (regex1, unit, 0, &match)) - g_clear_pointer (&match, g_match_info_unref); + if (!peer_died_cb) + return; - if (match == NULL && !g_regex_match (regex2, unit, 0, &match)) - g_clear_pointer (&match, g_match_info_unref); + g_variant_get (parameters, "(&s&s&s)", &name, &from, &to); - if (match != NULL) - { - g_autofree char *escaped_app_id = NULL; - /* Unescape the unit name which may have \x hex codes in it, e.g. - * "app-gnome-org.gnome.Evolution\x2dalarm\x2dnotify-2437.scope" - */ - escaped_app_id = g_match_info_fetch (match, 1); - if (cunescape (escaped_app_id, UNESCAPE_RELAX, &app_id) < 0) - app_id = g_strdup (""); - } - else - { - app_id = g_strdup (""); - } + if (name[0] != ':' || + strcmp (name, from) != 0 || + strcmp (to, "") != 0) + return; - return g_steal_pointer (&app_id); + peer_died_cb (name); } -#endif /* HAVE_LIBSYSTEMD */ void -set_appid_from_pid (XdpAppInfo *app_info, pid_t pid) +xdp_connection_track_name_owners (GDBusConnection *connection, + XdpPeerDiedCallback peer_died_cb) { -#ifdef HAVE_LIBSYSTEMD - g_autofree char *unit = NULL; - int res; + g_dbus_connection_signal_subscribe (connection, + DBUS_NAME_DBUS, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged", + DBUS_PATH_DBUS, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + name_owner_changed, + peer_died_cb, NULL); +} - g_return_if_fail (app_info->id == NULL); +gboolean +xdp_filter_options (GVariant *options, + GVariantBuilder *filtered, + const XdpOptionKey *supported_options, + int n_supported_options, + GError **error) +{ + int i; + gboolean ret = TRUE; - res = sd_pid_get_user_unit (pid, &unit); - /* - * The session might not be managed by systemd or there could be an error - * fetching our own systemd units or the unit might not be started by the - * desktop environment (e.g. it's a script run from terminal). - */ - if (res == -ENODATA || res < 0 || !unit || !g_str_has_prefix (unit, "app-")) + for (i = 0; i < n_supported_options; i++) { - app_info->id = g_strdup (""); - return; - } - - app_info->id = _xdp_parse_app_id_from_unit_name (unit); - g_debug ("Assigning app ID \"%s\" to pid %ld which has unit \"%s\"", - app_info->id, (long) pid, unit); + g_autoptr(GVariant) value = NULL; -#else - app_info->id = g_strdup (""); -#endif /* HAVE_LIBSYSTEMD */ -} + value = g_variant_lookup_value (options, + supported_options[i].key, + supported_options[i].type); + if (!value) + { + value = g_variant_lookup_value (options, supported_options[i].key, NULL); + if (value) + { + if (error && *error == NULL) + g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, + "Expected type '%s' for option '%s', got '%s'", + g_variant_type_peek_string (supported_options[i].type), + supported_options[i].key, + g_variant_type_peek_string (g_variant_get_type (value))); + ret = FALSE; + } -static XdpAppInfo * -xdp_app_info_new_host (pid_t pid) -{ - XdpAppInfo *app_info = xdp_app_info_new (XDP_APP_INFO_KIND_HOST); - set_appid_from_pid (app_info, pid); - return app_info; -} + continue; + } -static void -xdp_app_info_free (XdpAppInfo *app_info) -{ - g_free (app_info->id); + if (supported_options[i].validate) + { + g_autoptr(GError) local_error = NULL; - switch (app_info->kind) - { - case XDP_APP_INFO_KIND_FLATPAK: - g_clear_pointer (&app_info->u.flatpak.keyfile, g_key_file_free); - break; + if (!supported_options[i].validate (supported_options[i].key, value, options, &local_error)) + { + if (error && *error == NULL) + g_propagate_error (error, g_steal_pointer (&local_error)); + ret = FALSE; - case XDP_APP_INFO_KIND_SNAP: - g_clear_pointer (&app_info->u.snap.keyfile, g_key_file_free); - break; + continue; + } + } - case XDP_APP_INFO_KIND_HOST: - default: - break; + g_variant_builder_add (filtered, "{sv}", supported_options[i].key, value); } - g_free (app_info); + return ret; } -XdpAppInfo * -xdp_app_info_ref (XdpAppInfo *app_info) +static const GDBusErrorEntry xdg_desktop_portal_error_entries[] = { + { XDG_DESKTOP_PORTAL_ERROR_FAILED, "org.freedesktop.portal.Error.Failed" }, + { XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, "org.freedesktop.portal.Error.InvalidArgument" }, + { XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND, "org.freedesktop.portal.Error.NotFound" }, + { XDG_DESKTOP_PORTAL_ERROR_EXISTS, "org.freedesktop.portal.Error.Exists" }, + { XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, "org.freedesktop.portal.Error.NotAllowed" }, + { XDG_DESKTOP_PORTAL_ERROR_CANCELLED, "org.freedesktop.portal.Error.Cancelled" }, + { XDG_DESKTOP_PORTAL_ERROR_WINDOW_DESTROYED, "org.freedesktop.portal.Error.WindowDestroyed" } +}; + +GQuark +xdg_desktop_portal_error_quark (void) { - g_return_val_if_fail (app_info != NULL, NULL); + static volatile gsize quark_volatile = 0; - g_atomic_int_inc (&app_info->ref_count); - return app_info; + g_dbus_error_register_error_domain ("xdg-desktop-portal-error-quark", + &quark_volatile, + xdg_desktop_portal_error_entries, + G_N_ELEMENTS (xdg_desktop_portal_error_entries)); + return (GQuark) quark_volatile; } +static char *documents_mountpoint = NULL; + void -xdp_app_info_unref (XdpAppInfo *app_info) +xdp_set_documents_mountpoint (const char *path) { - g_return_if_fail (app_info != NULL); - - if (g_atomic_int_dec_and_test (&app_info->ref_count)) - xdp_app_info_free (app_info); + g_clear_pointer (&documents_mountpoint, g_free); + documents_mountpoint = g_strdup (path); } const char * -xdp_app_info_get_id (XdpAppInfo *app_info) -{ - g_return_val_if_fail (app_info != NULL, NULL); - - return app_info->id; -} - -XdpAppInfoKind -xdp_app_info_get_kind (XdpAppInfo *app_info) +xdp_get_documents_mountpoint (void) { - g_return_val_if_fail (app_info != NULL, -1); - - return app_info->kind; + return documents_mountpoint; } -GAppInfo * -xdp_app_info_load_app_info (XdpAppInfo *app_info) +/* alternate_document_path converts a file path */ +char * +xdp_get_alternate_document_path (const char *path, + const char *app_id) { - g_autofree char *desktop_id = NULL; + int len; - g_return_val_if_fail (app_info != NULL, NULL); + if (g_str_equal (app_id, "")) + return NULL; - switch (app_info->kind) - { - case XDP_APP_INFO_KIND_FLATPAK: - desktop_id = g_strconcat (app_info->id, ".desktop", NULL); - break; - - case XDP_APP_INFO_KIND_SNAP: - desktop_id = g_key_file_get_string (app_info->u.snap.keyfile, - SNAP_METADATA_GROUP_INFO, - SNAP_METADATA_KEY_DESKTOP_FILE, - NULL); - break; - - case XDP_APP_INFO_KIND_HOST: - default: - desktop_id = NULL; - break; - } + /* If we don't know where the document portal is mounted, then there + * is no alternate path */ + if (documents_mountpoint == NULL) + return NULL; - if (desktop_id == NULL) + /* If the path is not within the document portal, then there is no + * alternative path */ + len = strlen (documents_mountpoint); + if (!g_str_has_prefix (path, documents_mountpoint) || path[len] != '/') return NULL; - return G_APP_INFO (g_desktop_app_info_new (desktop_id)); + return g_strconcat (documents_mountpoint, "/by-app/", app_id, &path[len], NULL); } static gboolean -needs_quoting (const char *arg) -{ - while (*arg != 0) - { - char c = *arg; - if (!g_ascii_isalnum (c) && - !(c == '-' || c == '/' || c == '~' || - c == ':' || c == '.' || c == '_' || - c == '=' || c == '@')) - return TRUE; - arg++; - } - return FALSE; -} - -static char * -maybe_quote (const char *arg, - gboolean quote_escape) +is_valid_name_character (gint c, gboolean allow_dash) { - if (!quote_escape || !needs_quoting (arg)) - return g_strdup (arg); - else - return g_shell_quote (arg); + return + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + (c == '_') || (allow_dash && c == '-'); } -char ** -xdp_app_info_rewrite_commandline (XdpAppInfo *app_info, - const char * const *commandline, - gboolean quote_escape) +/* This is the same as flatpak apps, except we also allow + names to start with digits, and two-element names so that ids of the form + snap.$snapname is allowed for all snap names. */ +gboolean +xdp_is_valid_app_id (const char *string) { - g_autoptr(GPtrArray) args = NULL; - - g_return_val_if_fail (app_info != NULL, NULL); - - if (app_info->kind == XDP_APP_INFO_KIND_HOST) - { - int i; - args = g_ptr_array_new_with_free_func (g_free); - for (i = 0; commandline && commandline[i]; i++) - g_ptr_array_add (args, maybe_quote (commandline[i], quote_escape)); - g_ptr_array_add (args, NULL); - return (char **)g_ptr_array_free (g_steal_pointer (&args), FALSE); - } - else if (app_info->kind == XDP_APP_INFO_KIND_FLATPAK) - { - args = g_ptr_array_new_with_free_func (g_free); - - g_ptr_array_add (args, g_strdup ("flatpak")); - g_ptr_array_add (args, g_strdup ("run")); - if (commandline && commandline[0]) - { - int i; - g_autofree char *quoted_command = NULL; + guint len; + const gchar *s; + const gchar *end; + const gchar *last_dot; + int dot_count; + gboolean last_element; - quoted_command = maybe_quote (commandline[0], quote_escape); + g_return_val_if_fail (string != NULL, FALSE); - g_ptr_array_add (args, g_strdup_printf ("--command=%s", quoted_command)); + len = strlen (string); + if (G_UNLIKELY (len == 0)) + return FALSE; - /* Always quote the app ID if quote_escape is enabled to make - * rewriting the file simpler in case the app is renamed. - */ - if (quote_escape) - g_ptr_array_add (args, g_shell_quote (app_info->id)); - else - g_ptr_array_add (args, g_strdup (app_info->id)); + if (G_UNLIKELY (len > 255)) + return FALSE; - for (i = 1; commandline[i]; i++) - g_ptr_array_add (args, maybe_quote (commandline[i], quote_escape)); - } - else if (quote_escape) - g_ptr_array_add (args, g_shell_quote (app_info->id)); - else - g_ptr_array_add (args, g_strdup (app_info->id)); - g_ptr_array_add (args, NULL); + end = string + len; - return (char **)g_ptr_array_free (g_steal_pointer (&args), FALSE); - } - else - return NULL; -} + last_dot = strrchr (string, '.'); + last_element = FALSE; -char * -xdp_app_info_get_tryexec_path (XdpAppInfo *app_info) -{ - g_return_val_if_fail (app_info != NULL, NULL); + s = string; + if (G_UNLIKELY (*s == '.')) + return FALSE; /* Name can't start with a period */ - if (app_info->kind == XDP_APP_INFO_KIND_FLATPAK) + dot_count = 0; + while (s != end) { - g_autofree char *original_app_path = NULL; - g_autofree char *tryexec_path = NULL; - g_autofree char *app_slash = NULL; - g_autofree char *app_path = NULL; - char *app_slash_pointer; - char *path; - - original_app_path = g_key_file_get_string (app_info->u.flatpak.keyfile, - FLATPAK_METADATA_GROUP_INSTANCE, - FLATPAK_METADATA_KEY_ORIGINAL_APP_PATH, NULL); - app_path = g_key_file_get_string (app_info->u.flatpak.keyfile, - FLATPAK_METADATA_GROUP_INSTANCE, - FLATPAK_METADATA_KEY_APP_PATH, NULL); - path = original_app_path ? original_app_path : app_path; - - if (path == NULL || *path == '\0') - return NULL; - - app_slash = g_strconcat ("app/", app_info->id, NULL); - - app_slash_pointer = strstr (path, app_slash); - if (app_slash_pointer == NULL) - return NULL; - - /* Terminate path after the flatpak installation path such as .local/share/flatpak/ */ - *app_slash_pointer = '\0'; - - /* Find the path to the wrapper script exported by Flatpak, which can be - * used in a desktop file's TryExec= - */ - tryexec_path = g_strconcat (path, "exports/bin/", app_info->id, NULL); - if (access (tryexec_path, X_OK) != 0) + if (*s == '.') { - g_debug ("Wrapper script unexpectedly not executable or nonexistent: %s", tryexec_path); - return NULL; + if (s == last_dot) + last_element = TRUE; + s += 1; + if (G_UNLIKELY (s == end)) + return FALSE; + dot_count++; } - return g_steal_pointer (&tryexec_path); + if (G_UNLIKELY (!is_valid_name_character (*s, last_element))) + return FALSE; + s += 1; } - else - return NULL; -} -char * -xdp_app_info_get_instance (XdpAppInfo *app_info) -{ - g_return_val_if_fail (app_info != NULL, NULL); - - if (app_info->kind != XDP_APP_INFO_KIND_FLATPAK) - return NULL; + if (G_UNLIKELY (dot_count < 1)) + return FALSE; - return g_key_file_get_string (app_info->u.flatpak.keyfile, - FLATPAK_METADATA_GROUP_INSTANCE, - FLATPAK_METADATA_KEY_INSTANCE_ID, - NULL); + return TRUE; } -gboolean -xdp_app_info_is_host (XdpAppInfo *app_info) +char * +xdp_get_app_id_from_desktop_id (const char *desktop_id) { - g_return_val_if_fail (app_info != NULL, FALSE); - - return app_info->kind == XDP_APP_INFO_KIND_HOST; + const gchar *suffix = ".desktop"; + if (g_str_has_suffix (desktop_id, suffix)) + return g_strndup (desktop_id, strlen (desktop_id) - strlen (suffix)); + else + return g_strdup (desktop_id); } -gboolean -xdp_app_info_supports_opath (XdpAppInfo *app_info) +char * +xdp_maybe_quote (const char *arg, + gboolean quote_escape) { - return - app_info->kind == XDP_APP_INFO_KIND_FLATPAK || - app_info->kind == XDP_APP_INFO_KIND_HOST; + if (quote_escape && needs_quoting (arg)) + return g_shell_quote (arg); + else + return g_strdup (arg); } char * -xdp_app_info_remap_path (XdpAppInfo *app_info, - const char *path) +xdp_maybe_quote_argv (const char *argv[], + gboolean quote_escape) { - if (app_info->kind == XDP_APP_INFO_KIND_FLATPAK) - { - g_autofree char *app_path = g_key_file_get_string (app_info->u.flatpak.keyfile, - FLATPAK_METADATA_GROUP_INSTANCE, - FLATPAK_METADATA_KEY_APP_PATH, NULL); - g_autofree char *runtime_path = g_key_file_get_string (app_info->u.flatpak.keyfile, - FLATPAK_METADATA_GROUP_INSTANCE, - FLATPAK_METADATA_KEY_RUNTIME_PATH, - NULL); - - /* For apps we translate /app and /usr to the installed locations. - Also, we need to rewrite to drop the /newroot prefix added by - bubblewrap for other files to work. See - https://github.com/projectatomic/bubblewrap/pull/172 - for a bit more information on the /newroot issue. - */ - - if (g_str_has_prefix (path, "/newroot/")) - path = path + strlen ("/newroot"); - - if (app_path != NULL && g_str_has_prefix (path, "/app/")) - return g_build_filename (app_path, path + strlen ("/app/"), NULL); - else if (runtime_path != NULL && g_str_has_prefix (path, "/usr/")) - return g_build_filename (runtime_path, path + strlen ("/usr/"), NULL); - else if (g_str_has_prefix (path, "/run/host/usr/")) - return g_build_filename ("/usr", path + strlen ("/run/host/usr/"), NULL); - else if (g_str_has_prefix (path, "/run/host/etc/")) - return g_build_filename ("/etc", path + strlen ("/run/host/etc/"), NULL); - else if (g_str_has_prefix (path, "/run/flatpak/app/")) - return g_build_filename (g_get_user_runtime_dir (), "app", - path + strlen ("/run/flatpak/app/"), NULL); - else if (g_str_has_prefix (path, "/run/flatpak/doc/")) - return g_build_filename (g_get_user_runtime_dir (), "doc", - path + strlen ("/run/flatpak/doc/"), NULL); - else if (g_str_has_prefix (path, "/var/config/")) - return g_build_filename (g_get_home_dir (), ".var", "app", - app_info->id, "config", - path + strlen ("/var/config/"), NULL); - else if (g_str_has_prefix (path, "/var/data/")) - return g_build_filename (g_get_home_dir (), ".var", "app", - app_info->id, "data", - path + strlen ("/var/data/"), NULL); - } + GString *res = g_string_new (""); + int i; + + for (i = 0; argv[i] != NULL; i++) + { + if (i != 0) + g_string_append_c (res, ' '); + + if (quote_escape && needs_quoting (argv[i])) + { + g_autofree char *quoted = g_shell_quote (argv[i]); + g_string_append (res, quoted); + } + else + g_string_append (res, argv[i]); + } - return g_strdup (path); + return g_string_free (res, FALSE); } -gboolean -xdp_app_info_has_network (XdpAppInfo *app_info) +typedef struct { - gboolean has_network; - - switch (app_info->kind) - { - case XDP_APP_INFO_KIND_FLATPAK: - { - g_auto(GStrv) shared = g_key_file_get_string_list (app_info->u.flatpak.keyfile, - "Context", "shared", - NULL, NULL); - if (shared) - has_network = g_strv_contains ((const char * const *)shared, "network"); - else - has_network = FALSE; - } - break; + GError *error; + GError *splice_error; + GMainLoop *loop; + int refs; +} SpawnData; - case XDP_APP_INFO_KIND_SNAP: - has_network = g_key_file_get_boolean (app_info->u.snap.keyfile, - SNAP_METADATA_GROUP_INFO, - SNAP_METADATA_KEY_NETWORK, NULL); - break; +static void +spawn_data_exit (SpawnData *data) +{ + data->refs--; + if (data->refs == 0) + g_main_loop_quit (data->loop); +} - case XDP_APP_INFO_KIND_HOST: - default: - has_network = TRUE; - break; - } +static void +spawn_output_spliced_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + SpawnData *data = user_data; - return has_network; + g_output_stream_splice_finish (G_OUTPUT_STREAM (obj), result, &data->splice_error); + spawn_data_exit (data); } static void -ensure_app_info_by_unique_name (void) +spawn_exit_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) { - if (app_info_by_unique_name == NULL) - app_info_by_unique_name = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, - (GDestroyNotify)xdp_app_info_unref); + SpawnData *data = user_data; + + g_subprocess_wait_check_finish (G_SUBPROCESS (obj), result, &data->error); + spawn_data_exit (data); } -/* Returns NULL with error set on failure, NULL with no error set if not a flatpak, and app-info otherwise */ -static XdpAppInfo * -parse_app_info_from_flatpak_info (int pid, GError **error) +char * +xdp_spawn (GError **error, + const char *argv0, + ...) { - g_autofree char *root_path = NULL; - int root_fd = -1; - int info_fd = -1; - struct stat stat_buf; - g_autoptr(GError) local_error = NULL; - g_autoptr(GMappedFile) mapped = NULL; - g_autoptr(GKeyFile) metadata = NULL; - g_autoptr(XdpAppInfo) app_info = NULL; - const char *group; - g_autofree char *id = NULL; - - root_path = g_strdup_printf ("/proc/%u/root", pid); - root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); - if (root_fd == -1) - { - if (errno == EACCES) - { - struct statfs buf; - - /* Access to the root dir isn't allowed. This can happen if the root is on a fuse - * filesystem, such as in a toolbox container. We will never have a fuse rootfs - * in the flatpak case, so in that case its safe to ignore this and - * continue to detect other types of apps. - */ - if (statfs (root_path, &buf) == 0 && - buf.f_type == 0x65735546) /* FUSE_SUPER_MAGIC */ - return NULL; - } + GPtrArray *args; + const char *arg; + va_list ap; + char *output; - /* Otherwise, we should be able to open the root dir. Probably the app died and - we're failing due to /proc/$pid not existing. In that case fail instead - of treating this as privileged. */ - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unable to open %s", root_path); - return NULL; - } + va_start (ap, argv0); + args = g_ptr_array_new (); + g_ptr_array_add (args, (char *) argv0); + while ((arg = va_arg (ap, const char *))) + g_ptr_array_add (args, (char *) arg); + g_ptr_array_add (args, NULL); + va_end (ap); - metadata = g_key_file_new (); + output = xdp_spawn_full ((const char * const *) args->pdata, -1, -1, error); - info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); - close (root_fd); - if (info_fd == -1) - { - if (errno == ENOENT) - { - /* No file => on the host, return NULL with no error */ - return NULL; - } + g_ptr_array_free (args, TRUE); - /* Some weird error => failure */ - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unable to open application info file"); - return NULL; - } + return output; +} - if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) - { - /* Some weird fd => failure */ - close (info_fd); - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unable to open application info file"); - return NULL; - } +char * +xdp_spawn_full (const char * const *argv, + int source_fd, + int target_fd, + GError **error) +{ + g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autoptr(GSubprocess) subp = NULL; + GInputStream *in; + g_autoptr(GOutputStream) out = NULL; + g_autoptr(GMainLoop) loop = NULL; + SpawnData data = {0}; + g_autofree char *commandline = NULL; - mapped = g_mapped_file_new_from_fd (info_fd, FALSE, &local_error); - if (mapped == NULL) - { - close (info_fd); - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Can't map .flatpak-info file: %s", local_error->message); - return NULL; - } + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); - if (!g_key_file_load_from_data (metadata, - g_mapped_file_get_contents (mapped), - g_mapped_file_get_length (mapped), - G_KEY_FILE_NONE, &local_error)) - { - close (info_fd); - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Can't load .flatpak-info file: %s", local_error->message); - return NULL; - } + if (source_fd != -1) + g_subprocess_launcher_take_fd (launcher, source_fd, target_fd); - group = "Application"; - if (g_key_file_has_group (metadata, "Runtime")) - group = "Runtime"; + commandline = xdp_maybe_quote_argv ((const char **)argv, TRUE); + g_debug ("Running: %s", commandline); - id = g_key_file_get_string (metadata, group, "name", error); - if (id == NULL || !xdp_is_valid_app_id (id)) - { - close (info_fd); - return NULL; - } + subp = g_subprocess_launcher_spawnv (launcher, argv, error); + + if (subp == NULL) + return NULL; - close (info_fd); + loop = g_main_loop_new (NULL, FALSE); - app_info = xdp_app_info_new (XDP_APP_INFO_KIND_FLATPAK); - app_info->id = g_steal_pointer (&id); - app_info->u.flatpak.keyfile = g_steal_pointer (&metadata); + data.loop = loop; + data.refs = 2; + + in = g_subprocess_get_stdout_pipe (subp); + out = g_memory_output_stream_new_resizable (); + g_output_stream_splice_async (out, + in, + G_OUTPUT_STREAM_SPLICE_NONE, + 0, + NULL, + spawn_output_spliced_cb, + &data); - return g_steal_pointer (&app_info); -} + g_subprocess_wait_async (subp, NULL, spawn_exit_cb, &data); -int -_xdp_parse_cgroup_file (FILE *f, gboolean *is_snap) -{ - ssize_t n; - g_autofree char *id = NULL; - g_autofree char *controller = NULL; - g_autofree char *cgroup = NULL; - size_t id_len = 0, controller_len = 0, cgroup_len = 0; + g_main_loop_run (loop); - g_return_val_if_fail(f != NULL, -1); - g_return_val_if_fail(is_snap != NULL, -1); + if (data.error) + { + g_propagate_error (error, g_steal_pointer (&data.error)); + g_clear_error (&data.splice_error); + return NULL; + } - *is_snap = FALSE; - do + if (out) { - n = getdelim (&id, &id_len, ':', f); - if (n == -1) break; - n = getdelim (&controller, &controller_len, ':', f); - if (n == -1) break; - n = getdelim (&cgroup, &cgroup_len, '\n', f); - if (n == -1) break; - - /* Only consider the freezer, systemd group or unified cgroup - * hierarchies */ - if ((strcmp (controller, "freezer:") == 0 || - strcmp (controller, "name=systemd:") == 0 || - strcmp (controller, ":") == 0) && - strstr (cgroup, "/snap.") != NULL) + if (data.splice_error) { - *is_snap = TRUE; - break; + g_propagate_error (error, g_steal_pointer (&data.splice_error)); + return NULL; } + + /* Null terminate */ + g_output_stream_write (out, "\0", 1, NULL, NULL); + g_output_stream_close (out, NULL, NULL); + return g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out)); } - while (n >= 0); - if (n < 0 && !feof(f)) return -1; + return NULL; +} - return 0; +char * +xdp_canonicalize_filename (const char *path) +{ + g_autoptr(GFile) file = g_file_new_for_path (path); + return g_file_get_path (file); } -static gboolean -pid_is_snap (pid_t pid, GError **error) +gboolean +xdp_has_path_prefix (const char *str, + const char *prefix) { - g_autofree char *cgroup_path = NULL;; - int fd; - FILE *f = NULL; - gboolean is_snap = FALSE; - int err = 0; + while (TRUE) + { + /* Skip consecutive slashes to reach next path + element */ + while (*str == '/') + str++; + while (*prefix == '/') + prefix++; - g_return_val_if_fail(pid > 0, FALSE); + /* No more prefix path elements? Done! */ + if (*prefix == 0) + return TRUE; - cgroup_path = g_strdup_printf ("/proc/%u/cgroup", (guint) pid); - fd = open (cgroup_path, O_RDONLY | O_CLOEXEC | O_NOCTTY); - if (fd == -1) - { - err = errno; - goto end; - } + /* Compare path element */ + while (*prefix != 0 && *prefix != '/') + { + if (*str != *prefix) + return FALSE; + str++; + prefix++; + } - f = fdopen (fd, "r"); - if (f == NULL) - { - err = errno; - goto end; + /* Matched prefix path element, + must be entire str path element */ + if (*str != '/' && *str != 0) + return FALSE; } +} - fd = -1; /* fd is now owned by f */ +#define VALIDATOR_INPUT_FD 3 +#define ICON_VALIDATOR_GROUP "Icon Validator" +#define SOUND_VALIDATOR_GROUP "Sound Validator" - if (_xdp_parse_cgroup_file (f, &is_snap) == -1) - err = errno; +static const char * +icon_type_to_string (XdpIconType icon_type) +{ + switch (icon_type) + { + case XDP_ICON_TYPE_DESKTOP: + return "desktop"; - fclose (f); + case XDP_ICON_TYPE_NOTIFICATION: + return "notification"; -end: - /* Silence ENOENT, treating it as "not a snap" */ - if (err != 0 && err != ENOENT) - { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (err), - "Could not parse cgroup info for pid %u: %s", (guint) pid, - g_strerror (err)); + default: + g_assert_not_reached (); } - return is_snap; } -/* Returns NULL with error set on failure, NULL with no error set if not a snap, and app-info otherwise */ -static XdpAppInfo * -parse_app_info_from_snap (pid_t pid, GError **error) +gboolean +xdp_validate_icon (XdpSealedFd *icon, + XdpIconType icon_type, + char **out_format, + char **out_size) { - g_autoptr(GError) local_error = NULL; - g_autofree char *pid_str = NULL; - const char *argv[] = { "snap", "routine", "portal-info", NULL, NULL }; + g_autofree char *format = NULL; + g_autoptr(GError) error = NULL; + const char *icon_validator = LIBEXECDIR "/xdg-desktop-portal-validate-icon"; + const char *args[7]; + int size; + size_t i; g_autofree char *output = NULL; - g_autoptr(GKeyFile) metadata = NULL; - g_autoptr(XdpAppInfo) app_info = NULL; - g_autofree char *snap_name = NULL; + g_autoptr(GKeyFile) key_file = NULL; - /* Check the process's cgroup membership to fail quickly for non-snaps */ - if (!pid_is_snap (pid, error)) return NULL; + if (g_getenv ("XDP_VALIDATE_ICON")) + icon_validator = g_getenv ("XDP_VALIDATE_ICON"); - pid_str = g_strdup_printf ("%u", (guint) pid); - argv[3] = pid_str; - if (!xdp_spawnv (NULL, &output, 0, error, argv)) + if (!g_file_test (icon_validator, G_FILE_TEST_EXISTS)) { - return NULL; + g_warning ("Icon validation: %s not found, rejecting icon by default.", icon_validator); + return FALSE; } - metadata = g_key_file_new (); - if (!g_key_file_load_from_data (metadata, output, -1, G_KEY_FILE_NONE, &local_error)) + i = 0; + args[i++] = icon_validator; + + if (g_getenv ("XDP_VALIDATE_ICON_INSECURE") == NULL) + args[i++] = "--sandbox"; + + args[i++] = "--fd"; + args[i++] = G_STRINGIFY (VALIDATOR_INPUT_FD); + args[i++] = "--ruleset"; + args[i++] = icon_type_to_string (icon_type); + g_assert (i < G_N_ELEMENTS (args)); + args[i++] = NULL; + + output = xdp_spawn_full (args, xdp_sealed_fd_dup_fd (icon), VALIDATOR_INPUT_FD, &error); + if (!output) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Can't read snap info for pid %u: %s", pid, local_error->message); - return NULL; + g_warning ("Icon validation: Rejecting icon because validator failed: %s", error->message); + return FALSE; } - snap_name = g_key_file_get_string (metadata, SNAP_METADATA_GROUP_INFO, - SNAP_METADATA_KEY_INSTANCE_NAME, error); - if (snap_name == NULL) + key_file = g_key_file_new (); + if (!g_key_file_load_from_data (key_file, output, -1, G_KEY_FILE_NONE, &error)) { - return NULL; + g_warning ("Icon validation: %s", error->message); + return FALSE; + } + if (!(format = g_key_file_get_string (key_file, ICON_VALIDATOR_GROUP, "format", &error))) + { + g_warning ("Icon validation: %s", error->message); + return FALSE; + } + if (!(size = g_key_file_get_integer (key_file, ICON_VALIDATOR_GROUP, "width", &error))) + { + g_warning ("Icon validation: %s", error->message); + return FALSE; } - app_info = xdp_app_info_new (XDP_APP_INFO_KIND_SNAP); - app_info->id = g_strconcat ("snap.", snap_name, NULL); - app_info->u.snap.keyfile = g_steal_pointer (&metadata); + if (out_format) + *out_format = g_steal_pointer (&format); + if (out_size) + *out_size = g_strdup_printf ("%d", size); - return g_steal_pointer (&app_info); + return TRUE; } - -XdpAppInfo * -xdp_get_app_info_from_pid (pid_t pid, - GError **error) +gboolean +xdp_validate_sound (XdpSealedFd *sound) { - g_autoptr(XdpAppInfo) app_info = NULL; - g_autoptr(GError) local_error = NULL; + const char *args[5]; + g_autoptr(GError) error = NULL; + g_autoptr(GKeyFile) key_file = NULL; + g_autofree char *output = NULL; + const char *sound_validator = LIBEXECDIR "/xdg-desktop-portal-validate-sound"; + gsize i; - app_info = parse_app_info_from_flatpak_info (pid, &local_error); - if (app_info == NULL && local_error) - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return NULL; - } + if (g_getenv ("XDP_VALIDATE_SOUND")) + sound_validator = g_getenv ("XDP_VALIDATE_SOUND"); - if (app_info == NULL) + if (!g_file_test (sound_validator, G_FILE_TEST_EXISTS)) { - app_info = parse_app_info_from_snap (pid, &local_error); - if (app_info == NULL && local_error) - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return NULL; - } + g_warning ("Sound validation: %s not found, rejecting sound by default.", sound_validator); + return FALSE; } - if (app_info == NULL) - app_info = xdp_app_info_new_host (pid); + i = 0; + args[i++] = sound_validator; - return g_steal_pointer (&app_info); -} + if (g_getenv ("XDP_VALIDATE_SOUND_INSECURE") == NULL) + args[i++] = "--sandbox"; -static XdpAppInfo * -lookup_cached_app_info_by_sender (const char *sender) -{ - XdpAppInfo *app_info = NULL; + args[i++] = "--fd"; + args[i++] = G_STRINGIFY (VALIDATOR_INPUT_FD); + g_assert (i < G_N_ELEMENTS (args)); + args[i++] = NULL; + + output = xdp_spawn_full (args, xdp_sealed_fd_dup_fd (sound), VALIDATOR_INPUT_FD, &error); + if (!output) + { + g_warning ("Sound validation: Rejecting sound because validator failed: %s", error->message); + return FALSE; + } - G_LOCK (app_infos); - if (app_info_by_unique_name) + key_file = g_key_file_new (); + if (!g_key_file_load_from_data (key_file, output, -1, G_KEY_FILE_NONE, &error)) { - app_info = g_hash_table_lookup (app_info_by_unique_name, sender); - if (app_info) - xdp_app_info_ref (app_info); + g_warning ("Sound validation: %s", error->message); + return FALSE; } - G_UNLOCK (app_infos); - return app_info; + return TRUE; } -static XdpAppInfo * -xdp_connection_lookup_app_info_sync (GDBusConnection *connection, - const char *sender, - GCancellable *cancellable, - GError **error) +gboolean +xdp_variant_contains_key (GVariant *dictionary, + const char *key) { - g_autoptr(GDBusMessage) msg = NULL; - g_autoptr(GDBusMessage) reply = NULL; - g_autoptr(XdpAppInfo) app_info = NULL; - GVariant *body; - guint32 pid = 0; - - app_info = lookup_cached_app_info_by_sender (sender); - if (app_info) - return g_steal_pointer (&app_info); - - msg = g_dbus_message_new_method_call (DBUS_NAME_DBUS, - DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS, - "GetConnectionUnixProcessID"); - g_dbus_message_set_body (msg, g_variant_new ("(s)", sender)); - - reply = g_dbus_connection_send_message_with_reply_sync (connection, msg, - G_DBUS_SEND_MESSAGE_FLAGS_NONE, - 30000, - NULL, - cancellable, - error); - if (reply == NULL) - return NULL; + GVariantIter iter; - if (g_dbus_message_get_message_type (reply) == G_DBUS_MESSAGE_TYPE_ERROR) + g_variant_iter_init (&iter, dictionary); + while (TRUE) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't find peer app id"); - return NULL; - } - - body = g_dbus_message_get_body (reply); - g_variant_get (body, "(u)", &pid); + g_autoptr(GVariant) entry = NULL; + g_autoptr(GVariant) entry_key = NULL; - app_info = xdp_get_app_info_from_pid (pid, error); - if (app_info == NULL) - return NULL; + entry = g_variant_iter_next_value (&iter); + if (!entry) + break; - G_LOCK (app_infos); - ensure_app_info_by_unique_name (); - g_hash_table_insert (app_info_by_unique_name, g_strdup (sender), - xdp_app_info_ref (app_info)); - G_UNLOCK (app_infos); + entry_key = g_variant_get_child_value (entry, 0); + if (g_strcmp0 (g_variant_get_string (entry_key, NULL), key) == 0) + return TRUE; + } - return g_steal_pointer (&app_info); + return FALSE; } -XdpAppInfo * -xdp_invocation_lookup_app_info_sync (GDBusMethodInvocation *invocation, - GCancellable *cancellable, - GError **error) +static int +parse_pid (const char *str, + pid_t *pid) { - GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); - const gchar *sender = g_dbus_method_invocation_get_sender (invocation); + char *end; + guint64 v; + pid_t p; - return xdp_connection_lookup_app_info_sync (connection, sender, cancellable, error); -} - -static void -name_owner_changed (GDBusConnection *connection, - const gchar *sender_name, - const gchar *object_path, - const gchar *interface_name, - const gchar *signal_name, - GVariant *parameters, - gpointer user_data) -{ - const char *name, *from, *to; - XdpPeerDiedCallback peer_died_cb = user_data; - - g_variant_get (parameters, "(&s&s&s)", &name, &from, &to); - - if (name[0] == ':' && - strcmp (name, from) == 0 && - strcmp (to, "") == 0) - { - G_LOCK (app_infos); - if (app_info_by_unique_name) - g_hash_table_remove (app_info_by_unique_name, name); - G_UNLOCK (app_infos); - - if (peer_died_cb) - peer_died_cb (name); - } -} - -void -xdp_connection_track_name_owners (GDBusConnection *connection, - XdpPeerDiedCallback peer_died_cb) -{ - g_dbus_connection_signal_subscribe (connection, - DBUS_NAME_DBUS, - DBUS_INTERFACE_DBUS, - "NameOwnerChanged", - DBUS_PATH_DBUS, - NULL, - G_DBUS_SIGNAL_FLAGS_NONE, - name_owner_changed, - peer_died_cb, NULL); -} - -gboolean -xdp_filter_options (GVariant *options, - GVariantBuilder *filtered, - XdpOptionKey *supported_options, - int n_supported_options, - GError **error) -{ - int i; - gboolean ret = TRUE; - - for (i = 0; i < n_supported_options; i++) - { - g_autoptr(GVariant) value = NULL; - - value = g_variant_lookup_value (options, - supported_options[i].key, - supported_options[i].type); - if (!value) - { - value = g_variant_lookup_value (options, supported_options[i].key, NULL); - if (value) - { - if (*error == NULL) - g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, - "Expected type '%s' for option '%s', got '%s'", - g_variant_type_peek_string (supported_options[i].type), - supported_options[i].key, - g_variant_type_peek_string (g_variant_get_type (value))); - ret = FALSE; - } - - continue; - } - - if (supported_options[i].validate) - { - g_autoptr(GError) local_error = NULL; - - if (!supported_options[i].validate (supported_options[i].key, value, options, &local_error)) - { - if (ret) - { - ret = FALSE; - if (error && *error == NULL) - { - g_propagate_error (error, local_error); - local_error = NULL; - } - } - - continue; - } - } - - g_variant_builder_add (filtered, "{sv}", supported_options[i].key, value); - } - - return ret; -} - -static const GDBusErrorEntry xdg_desktop_portal_error_entries[] = { - { XDG_DESKTOP_PORTAL_ERROR_FAILED, "org.freedesktop.portal.Error.Failed" }, - { XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, "org.freedesktop.portal.Error.InvalidArgument" }, - { XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND, "org.freedesktop.portal.Error.NotFound" }, - { XDG_DESKTOP_PORTAL_ERROR_EXISTS, "org.freedesktop.portal.Error.Exists" }, - { XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED, "org.freedesktop.portal.Error.NotAllowed" }, - { XDG_DESKTOP_PORTAL_ERROR_CANCELLED, "org.freedesktop.portal.Error.Cancelled" }, - { XDG_DESKTOP_PORTAL_ERROR_WINDOW_DESTROYED, "org.freedesktop.portal.Error.WindowDestroyed" } -}; - -GQuark -xdg_desktop_portal_error_quark (void) -{ - static volatile gsize quark_volatile = 0; - - g_dbus_error_register_error_domain ("xdg-desktop-portal-error-quark", - &quark_volatile, - xdg_desktop_portal_error_entries, - G_N_ELEMENTS (xdg_desktop_portal_error_entries)); - return (GQuark) quark_volatile; -} - -static char *documents_mountpoint = NULL; - -static char * -verify_proc_self_fd (XdpAppInfo *app_info, - const char *proc_path, - GError **error) -{ - char path_buffer[PATH_MAX + 1]; - ssize_t symlink_size; - int saved_errno; - - symlink_size = readlink (proc_path, path_buffer, PATH_MAX); - if (symlink_size < 0) - { - saved_errno = errno; - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), - "readlink %s: %s", proc_path, g_strerror (saved_errno)); - return NULL; - } - - path_buffer[symlink_size] = 0; - - /* All normal paths start with /, but some weird things - don't, such as socket:[27345] or anon_inode:[eventfd]. - We don't support any of these */ - if (path_buffer[0] != '/') - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, - "Not a regular file or directory: %s", path_buffer); - return NULL; - } - - /* File descriptors to actually deleted files have " (deleted)" - appended to them. This also happens to some fake fd types - like shmem which are "/ (deleted)". All such - files are considered invalid. Unfortunately this also - matches files with filenames that actually end in " (deleted)", - but there is not much to do about this. */ - if (g_str_has_suffix (path_buffer, " (deleted)")) - { - if (documents_mountpoint != NULL && - g_str_has_prefix (path_buffer, documents_mountpoint)) - { - /* Unfortunately our workaround for dcache purging triggers - o_path file descriptors on the fuse filesystem being - marked as deleted, so we have to allow these here and - rewrite them. This is safe, becase we will stat the file - and compare to make sure we end up on the right file. */ - path_buffer[symlink_size - strlen(" (deleted)")] = 0; - } - else - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, - "Cannot share deleted file: %s", path_buffer); - return NULL; - } - } - - /* remap from sandbox to host if needed */ - return xdp_app_info_remap_path (app_info, path_buffer); -} - -void -xdp_set_documents_mountpoint (const char *path) -{ - g_clear_pointer (&documents_mountpoint, g_free); - documents_mountpoint = g_strdup (path); -} - -/* alternate_document_path converts a file path */ -char * -xdp_get_alternate_document_path (const char *path, const char *app_id) -{ - int len; - - if (g_str_equal (app_id, "")) - return NULL; - - /* If we don't know where the document portal is mounted, then there - * is no alternate path */ - if (documents_mountpoint == NULL) - return NULL; - - /* If the path is not within the document portal, then there is no - * alternative path */ - len = strlen (documents_mountpoint); - if (!g_str_has_prefix (path, documents_mountpoint) || path[len] != '/') - return NULL; - - return g_strconcat (documents_mountpoint, "/by-app/", app_id, &path[len], NULL); -} - -static gboolean -check_same_file (const char *path, - struct stat *expected_st_buf, - GError **error) -{ - struct stat real_st_buf; - int saved_errno; - - if (stat (path, &real_st_buf) < 0) - { - saved_errno = errno; - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), - "stat %s: %s", path, g_strerror (saved_errno)); - return FALSE; - } - - if (expected_st_buf->st_dev != real_st_buf.st_dev || - expected_st_buf->st_ino != real_st_buf.st_ino) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "\"%s\" identity (%ju,%ju) does not match expected (%ju,%ju)", - path, - (uintmax_t) expected_st_buf->st_dev, - (uintmax_t) expected_st_buf->st_ino, - (uintmax_t) real_st_buf.st_dev, - (uintmax_t) real_st_buf.st_ino); - return FALSE; - } - - return TRUE; -} - -char * -xdp_app_info_get_path_for_fd (XdpAppInfo *app_info, - int fd, - int require_st_mode, - struct stat *st_buf, - gboolean *writable_out, - GError **error) -{ - g_autofree char *proc_path = NULL; - int fd_flags; - struct stat st_buf_store; - gboolean writable = FALSE; - g_autofree char *path = NULL; - int saved_errno; - - if (st_buf == NULL) - st_buf = &st_buf_store; - - if (fd == -1) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "Invalid file descriptor"); - return NULL; - } - - /* Must be able to get fd flags */ - fd_flags = fcntl (fd, F_GETFL); - if (fd_flags == -1) - { - saved_errno = errno; - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), - "Cannot get file descriptor flags (fcntl F_GETFL: %s)", - g_strerror (saved_errno)); - return NULL; - } - - /* Must be able to fstat */ - if (fstat (fd, st_buf) < 0) - { - saved_errno = errno; - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), - "Cannot get file information (fstat: %s)", - g_strerror (saved_errno)); - return NULL; - } - - /* Verify mode */ - if (require_st_mode != 0 && - (st_buf->st_mode & S_IFMT) != require_st_mode) - { - switch (require_st_mode) - { - case S_IFDIR: - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, - "File type 0o%o is not a directory", - (st_buf->st_mode & S_IFMT)); - return NULL; - - case S_IFREG: - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, - "File type 0o%o is not a regular file", - (st_buf->st_mode & S_IFMT)); - return NULL; - - default: - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "File type 0o%o does not match expected 0o%o", - (st_buf->st_mode & S_IFMT), require_st_mode); - return NULL; - } - } - - proc_path = g_strdup_printf ("/proc/self/fd/%d", fd); - - /* Must be able to read valid path from /proc/self/fd */ - /* This is an absolute and (at least at open time) symlink-expanded path */ - path = verify_proc_self_fd (app_info, proc_path, error); - if (path == NULL) - return NULL; - - if ((fd_flags & O_PATH) == O_PATH) - { - int read_access_mode; - - /* Earlier versions of the portal supported only O_PATH fds, as - * these are safer to handle on the portal side. But we now - * prefer regular FDs because these ensure that the sandbox - * actually has full access to the file in its security context. - * - * However, we still support O_PATH fds when possible because - * existing code uses it. - * - * See issues #167 for details. - */ - - /* Must not be O_NOFOLLOW (because we want the target file) */ - if ((fd_flags & O_NOFOLLOW) == O_NOFOLLOW) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "O_PATH fd was opened O_NOFOLLOW"); - return NULL; - } - - if (!xdp_app_info_supports_opath (app_info)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "App \"%s\" of type %d does not support O_PATH fd passing", - app_info->id, app_info->kind); - return NULL; - } - - read_access_mode = R_OK; - if (S_ISDIR (st_buf->st_mode)) - read_access_mode |= X_OK; - - /* Must be able to access the path via the sandbox supplied O_PATH fd, - which applies the sandbox side mount options (like readonly). */ - if (access (proc_path, read_access_mode) != 0) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, - "\"%s\" not available for read access via \"%s\"", - path, proc_path); - return NULL; - } - - if (xdp_app_info_is_host (app_info) || access (proc_path, W_OK) == 0) - writable = TRUE; - } - else /* Regular file with no O_PATH */ - { - int accmode = fd_flags & O_ACCMODE; - - /* Note that this only gives valid results for writable for regular files, - as there is no way to get a writable fd for a directory. */ - - /* Don't allow WRONLY (or weird) open modes */ - if (accmode != O_RDONLY && - accmode != O_RDWR) - return NULL; - - if (xdp_app_info_is_host (app_info) || accmode == O_RDWR) - writable = TRUE; - } - - /* Verify that this is the same file as the app opened */ - if (!check_same_file (path, st_buf, error)) - { - /* If the path is provided by the document portal, the inode - number will not match, due to only a subtree being mounted in - the sandbox. So we check to see if the equivalent path - within that subtree matches our file descriptor. - - If the alternate path doesn't match either, then we treat it - as a failure. - */ - g_autofree char *alt_path = NULL; - alt_path = xdp_get_alternate_document_path (path, xdp_app_info_get_id (app_info)); - - if (alt_path == NULL) - return NULL; - - g_clear_error (error); - - if (!check_same_file (alt_path, st_buf, error)) - return NULL; - } - - if (writable_out) - *writable_out = writable; - - return g_steal_pointer (&path); -} - -static gboolean -is_valid_name_character (gint c, gboolean allow_dash) -{ - return - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9') || - (c == '_') || (allow_dash && c == '-'); -} - -/* This is the same as flatpak apps, except we also allow - names to start with digits, and two-element names so that ids of the form - snap.$snapname is allowed for all snap names. */ -gboolean -xdp_is_valid_app_id (const char *string) -{ - guint len; - const gchar *s; - const gchar *end; - const gchar *last_dot; - int dot_count; - gboolean last_element; - - g_return_val_if_fail (string != NULL, FALSE); - - len = strlen (string); - if (G_UNLIKELY (len == 0)) - return FALSE; - - if (G_UNLIKELY (len > 255)) - return FALSE; - - end = string + len; - - last_dot = strrchr (string, '.'); - last_element = FALSE; - - s = string; - if (G_UNLIKELY (*s == '.')) - return FALSE; /* Name can't start with a period */ - - dot_count = 0; - while (s != end) - { - if (*s == '.') - { - if (s == last_dot) - last_element = TRUE; - s += 1; - if (G_UNLIKELY (s == end)) - return FALSE; - dot_count++; - } - - if (G_UNLIKELY (!is_valid_name_character (*s, last_element))) - return FALSE; - s += 1; - } - - if (G_UNLIKELY (dot_count < 1)) - return FALSE; - - return TRUE; -} - -char * -xdp_get_app_id_from_desktop_id (const char *desktop_id) -{ - const gchar *suffix = ".desktop"; - if (g_str_has_suffix (desktop_id, suffix)) - return g_strndup (desktop_id, strlen (desktop_id) - strlen (suffix)); - else - return g_strdup (desktop_id); -} - -char * -xdp_quote_argv (const char *argv[]) -{ - GString *res = g_string_new (""); - int i; - - for (i = 0; argv[i] != NULL; i++) - { - if (i != 0) - g_string_append_c (res, ' '); - - if (needs_quoting (argv[i])) - { - g_autofree char *quoted = g_shell_quote (argv[i]); - g_string_append (res, quoted); - } - else - g_string_append (res, argv[i]); - } - - return g_string_free (res, FALSE); -} - -typedef struct -{ - GError *error; - GError *splice_error; - GMainLoop *loop; - int refs; -} SpawnData; - -static void -spawn_data_exit (SpawnData *data) -{ - data->refs--; - if (data->refs == 0) - g_main_loop_quit (data->loop); -} - -static void -spawn_output_spliced_cb (GObject *obj, - GAsyncResult *result, - gpointer user_data) -{ - SpawnData *data = user_data; - - g_output_stream_splice_finish (G_OUTPUT_STREAM (obj), result, &data->splice_error); - spawn_data_exit (data); -} - -static void -spawn_exit_cb (GObject *obj, - GAsyncResult *result, - gpointer user_data) -{ - SpawnData *data = user_data; - - g_subprocess_wait_check_finish (G_SUBPROCESS (obj), result, &data->error); - spawn_data_exit (data); -} - -gboolean -xdp_spawn (GFile *dir, - char **output, - GSubprocessFlags flags, - GError **error, - const gchar *argv0, - va_list ap) -{ - GPtrArray *args; - const gchar *arg; - gboolean res; - - args = g_ptr_array_new (); - g_ptr_array_add (args, (gchar *) argv0); - while ((arg = va_arg (ap, const gchar *))) - g_ptr_array_add (args, (gchar *) arg); - g_ptr_array_add (args, NULL); - - res = xdp_spawnv (dir, output, flags, error, (const gchar * const *) args->pdata); - - g_ptr_array_free (args, TRUE); - - return res; -} - -gboolean -xdp_spawnv (GFile *dir, - char **output, - GSubprocessFlags flags, - GError **error, - const gchar * const *argv) -{ - g_autoptr(GSubprocessLauncher) launcher = NULL; - g_autoptr(GSubprocess) subp = NULL; - GInputStream *in; - g_autoptr(GOutputStream) out = NULL; - g_autoptr(GMainLoop) loop = NULL; - SpawnData data = {0}; - g_autofree gchar *commandline = NULL; - - launcher = g_subprocess_launcher_new (0); - - if (output) - flags |= G_SUBPROCESS_FLAGS_STDOUT_PIPE; - - g_subprocess_launcher_set_flags (launcher, flags); - - if (dir) - { - g_autofree char *path = g_file_get_path (dir); - g_subprocess_launcher_set_cwd (launcher, path); - } - - commandline = xdp_quote_argv ((const char **)argv); - g_debug ("Running: %s", commandline); - - subp = g_subprocess_launcher_spawnv (launcher, argv, error); - - if (subp == NULL) - return FALSE; - - loop = g_main_loop_new (NULL, FALSE); - - data.loop = loop; - data.refs = 1; - - if (output) - { - data.refs++; - in = g_subprocess_get_stdout_pipe (subp); - out = g_memory_output_stream_new_resizable (); - g_output_stream_splice_async (out, - in, - G_OUTPUT_STREAM_SPLICE_NONE, - 0, - NULL, - spawn_output_spliced_cb, - &data); - } - - g_subprocess_wait_async (subp, NULL, spawn_exit_cb, &data); - - g_main_loop_run (loop); - - if (data.error) - { - g_propagate_error (error, data.error); - g_clear_error (&data.splice_error); - return FALSE; - } - - if (out) - { - if (data.splice_error) - { - g_propagate_error (error, data.splice_error); - return FALSE; - } - - /* Null terminate */ - g_output_stream_write (out, "\0", 1, NULL, NULL); - g_output_stream_close (out, NULL, NULL); - *output = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out)); - } - - return TRUE; -} - -char * -xdp_canonicalize_filename (const char *path) -{ - g_autoptr(GFile) file = g_file_new_for_path (path); - return g_file_get_path (file); -} - -gboolean -xdp_has_path_prefix (const char *str, - const char *prefix) -{ - while (TRUE) - { - /* Skip consecutive slashes to reach next path - element */ - while (*str == '/') - str++; - while (*prefix == '/') - prefix++; - - /* No more prefix path elements? Done! */ - if (*prefix == 0) - return TRUE; - - /* Compare path element */ - while (*prefix != 0 && *prefix != '/') - { - if (*str != *prefix) - return FALSE; - str++; - prefix++; - } - - /* Matched prefix path element, - must be entire str path element */ - if (*str != '/' && *str != 0) - return FALSE; - } -} - -/* pid mapping code */ -static int -parse_pid (const char *str, - pid_t *pid) -{ - char *end; - guint64 v; - pid_t p; - - errno = 0; - v = g_ascii_strtoull (str, &end, 0); - if (end == str) - return -ENOENT; - else if (errno != 0) - return -errno; + errno = 0; + v = g_ascii_strtoull (str, &end, 0); + if (end == str) + return -ENOENT; + else if (errno != 0) + return -errno; p = (pid_t) v; if (p < 1 || (guint64) p != v) - return -ERANGE; - - if (pid) - *pid = p; - - return 0; -} - -static int -parse_status_field_pid (const char *val, - pid_t *pid) -{ - const char *t; - - t = strrchr (val, '\t'); - if (t == NULL) - return -ENOENT; - - return parse_pid (t, pid); -} - -static int -parse_status_field_uid (const char *val, - uid_t *uid) -{ - const char *t; - char *end; - guint64 v; - uid_t u; - - t = strrchr (val, '\t'); - if (t == NULL) - return -ENOENT; - - errno = 0; - v = g_ascii_strtoull (t, &end, 0); - if (end == val) - return -ENOENT; - else if (errno != 0) - return -errno; - - u = (uid_t) v; - - if ((guint64) u != v) - return -ERANGE; - - if (uid) - *uid = u; - - return 0; -} - -static int -parse_status_file (int pid_fd, - pid_t *pid_out, - uid_t *uid_out) -{ - g_autofree char *key = NULL; - g_autofree char *val = NULL; - gboolean have_pid = pid_out == NULL; - gboolean have_uid = uid_out == NULL; - FILE *f; - size_t keylen = 0; - size_t vallen = 0; - ssize_t n; - int fd; - int r = 0; - - g_return_val_if_fail (pid_fd > -1, FALSE); - - fd = openat (pid_fd, "status", O_RDONLY | O_CLOEXEC | O_NOCTTY); - if (fd == -1) - return -errno; - - f = fdopen (fd, "r"); - - if (f == NULL) - return -errno; - - fd = -1; /* fd is now owned by f */ - - do { - n = getdelim (&key, &keylen, ':', f); - if (n == -1) - { - r = -errno; - break; - } - - n = getdelim (&val, &vallen, '\n', f); - if (n == -1) - { - r = -errno; - break; - } - - g_strstrip (key); - g_strstrip (val); - - if (!strncmp (key, "NSpid", strlen ("NSpid"))) - { - r = parse_status_field_pid (val, pid_out); - have_pid = r > -1; - } - else if (!strncmp (key, "Uid", strlen ("Uid"))) - { - r = parse_status_field_uid (val, uid_out); - have_uid = r > -1; - } - - if (r < 0) - g_warning ("Failed to parse 'status::%s': %s", - key, g_strerror (-r)); - - } while (r == 0 && (!have_uid || !have_pid)); - - fclose (f); - - if (r != 0) - return r; - else if (!have_uid || !have_pid) - return -ENXIO; /* ENOENT for the fields */ - - return 0; -} - -static int -lookup_ns_from_pid_fd (int pid_fd, - ino_t *ns) -{ - struct stat st; - int r; - - g_return_val_if_fail (ns != NULL, -1); - - r = fstatat (pid_fd, "ns/pid", &st, 0); - if (r == -1) - return -errno; - - /* The inode number (together with the device ID) encode - * the identity of the pid namespace, see namespaces(7) - */ - *ns = st.st_ino; - - return 0; -} - -static int -open_pid_fd (int proc_fd, - pid_t pid, - GError **error) -{ - char buf[20] = {0, }; - int fd; - - snprintf (buf, sizeof(buf), "%u", (guint) pid); - - fd = openat (proc_fd, buf, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); - - if (fd == -1) - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), - "Could not to open '/proc/pid/%u': %s", (guint) pid, - g_strerror (errno)); + return -ERANGE; - return fd; + if (pid) + *pid = p; + + return 0; } static int -open_fdinfo_dir (GError **error) +parse_status_field_pid (const char *val, + pid_t *pid) { - int fd; - - fd = open ("/proc/self/fdinfo", O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + const char *t; - if (fd < 0) - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), - "Could not to open /proc/self/fdinfo: %s", - g_strerror (errno)); + /* Takes "Pid: 12345" */ + t = strrchr (val, '\t'); + g_assert (t); - return fd; + return parse_pid (t, pid); } -static inline gboolean -find_pid (pid_t *pids, - guint n_pids, - pid_t want, - guint *idx) -{ - for (guint i = 0; i < n_pids; i++) - { - if (pids[i] == want) - { - *idx = i; - return TRUE; - } - } - - return FALSE; -} -static gboolean -map_pids (DIR *proc, - ino_t pidns, - pid_t *pids, - guint n_pids, - uid_t target_uid, - GError **error) +static int +parse_status_field_nspid (const char *val, + pid_t *pid) { - pid_t *res = NULL; - struct dirent *de; - guint count = 0; - - res = g_alloca (sizeof (pid_t) * n_pids); - memset (res, 0, sizeof (pid_t) * n_pids); - - while ((de = readdir (proc)) != NULL) - { - xdp_autofd int pid_fd = -1; - pid_t outside = 0; - pid_t inside = 0; - uid_t uid = 0; - guint idx; - ino_t ns = 0; - int r; - - if (de->d_type != DT_DIR) - continue; - - pid_fd = openat (dirfd (proc), de->d_name, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); - if (pid_fd == -1) - continue; - - r = lookup_ns_from_pid_fd (pid_fd, &ns); - if (r < 0) - continue; - - if (pidns != ns) - continue; - - r = parse_pid (de->d_name, &outside); - if (r < 0) - continue; - - r = parse_status_file (pid_fd, &inside, &uid); - if (r < 0) - continue; - - if (!find_pid (pids, n_pids, inside, &idx)) - continue; - - /* We got a match, let's make sure the real uids match as well */ - if (uid != target_uid) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, - "Matching pid doesn't belong to the target user"); - return FALSE; - } - - /* this handles the first occurrence, already identified by find_pid, - * as well as duplicate entries */ - for (guint i = idx; i < n_pids; i++) - { - if (pids[i] == inside) - { - res[idx] = outside; - count++; - } - } - } - - if (count != n_pids) - { - g_autoptr(GString) str = NULL; - - str = g_string_new ("Process ids could not be found: "); - - for (guint i = 0; i < n_pids; i++) - if (res[i] == 0) - g_string_append_printf (str, "%d, ", (guint32) pids[i]); - - g_string_truncate (str, str->len - 2); - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, str->str); - - return FALSE; - } + const char *t; - memcpy (pids, res, sizeof (pid_t) * n_pids); + /* This is a tab separated list of namespaces. + * We only want the innermost namespace. */ + t = strrchr (val, '\t'); + if (t != NULL) + val = t; - return TRUE; + return parse_pid (val, pid); } -static gboolean -pidfd_to_pid (int fdinfo, const int pidfd, pid_t *pid, GError **error) +static pid_t +pidfd_to_pid (int fdinfo, + const int pidfd, + GError **error) { g_autofree char *name = NULL; g_autofree char *key = NULL; @@ -1960,8 +786,7 @@ pidfd_to_pid (int fdinfo, const int pidfd, pid_t *pid, GError **error) ssize_t n; int fd; int r = 0; - - *pid = 0; + pid_t pid = -1; name = g_strdup_printf ("%d", pidfd); @@ -1997,7 +822,7 @@ pidfd_to_pid (int fdinfo, const int pidfd, pid_t *pid, GError **error) if (!strncmp (key, "Pid", 3)) { - r = parse_status_field_pid (val, pid); + r = parse_status_field_pid (val, &pid); found = r > -1; } @@ -2017,188 +842,325 @@ pidfd_to_pid (int fdinfo, const int pidfd, pid_t *pid, GError **error) "Could not parse fdinfo: Pid field missing"); } - return found; + return pid; +} + +static int +open_fdinfo_dir (GError **error) +{ + int fd; + + fd = open ("/proc/self/fdinfo", O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + + if (fd < 0) + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Could not to open /proc/self/fdinfo: %s", + g_strerror (errno)); + + return fd; +} + +pid_t +xdp_pidfd_to_pid (int pidfd, + GError **error) +{ + int fdinfo = -1; + pid_t pid; + + g_return_val_if_fail (pidfd >= 0, -1); + + fdinfo = open_fdinfo_dir (error); + if (fdinfo == -1) + return -1; + + pid = pidfd_to_pid (fdinfo, pidfd, error); + (void) close (fdinfo); + + return pid; +} + +gboolean +xdp_pidfds_to_pids (const int *pidfds, + pid_t *pids, + gint count, + GError **error) +{ + int fdinfo = -1; + int i; + + g_return_val_if_fail (pidfds != NULL, FALSE); + g_return_val_if_fail (pids != NULL, FALSE); + + fdinfo = open_fdinfo_dir (error); + if (fdinfo == -1) + return FALSE; + + for (i = 0; i < count; i++) + { + pids[i] = pidfd_to_pid (fdinfo, pidfds[i], error); + if (pids[i] < 0) + break; + } + + (void) close (fdinfo); + + return i == count; +} + +gboolean +xdp_pidfd_get_namespace (int pidfd, + ino_t *ns, + GError **error) +{ + struct stat st; + int r; + + g_return_val_if_fail (pidfd >= 0, FALSE); + g_return_val_if_fail (ns != NULL, FALSE); + + r = fstatat (pidfd, "ns/pid", &st, 0); + if (r == -1) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Could not fstatat ns/pid: %s", + g_strerror (errno)); + return FALSE; + } + + /* The inode number (together with the device ID) encode + * the identity of the pid namespace, see namespaces(7) + */ + *ns = st.st_ino; + return TRUE; +} + +static int +parse_status_field_uid (const char *val, + uid_t *uid) +{ + const char *t; + char *end; + guint64 v; + uid_t u; + + t = strrchr (val, '\t'); + if (t == NULL) + return -ENOENT; + + errno = 0; + v = g_ascii_strtoull (t, &end, 0); + if (end == val) + return -ENOENT; + else if (errno != 0) + return -errno; + + u = (uid_t) v; + + if ((guint64) u != v) + return -ERANGE; + + if (uid) + *uid = u; + + return 0; } -static JsonNode * -xdp_app_info_load_bwrap_info (XdpAppInfo *app_info, - GError **error) +static int +parse_status_file (int pid_fd, + pid_t *pid_out, + uid_t *uid_out) { - g_autoptr(JsonParser) parser = NULL; - g_autoptr(JsonNode) root = NULL; - g_autofree char *instance = NULL; - g_autofree char *data = NULL; - gsize len; - char *path; + g_autofree char *key = NULL; + g_autofree char *val = NULL; + gboolean have_pid = pid_out == NULL; + gboolean have_uid = uid_out == NULL; + FILE *f; + size_t keylen = 0; + size_t vallen = 0; + ssize_t n; + int fd; + int r = 0; + + g_return_val_if_fail (pid_fd > -1, FALSE); + + fd = openat (pid_fd, "status", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd == -1) + return -errno; + + f = fdopen (fd, "r"); + + if (f == NULL) + return -errno; + + fd = -1; /* fd is now owned by f */ + + do { + n = getdelim (&key, &keylen, ':', f); + if (n == -1) + { + r = -errno; + break; + } + + n = getdelim (&val, &vallen, '\n', f); + if (n == -1) + { + r = -errno; + break; + } - g_return_val_if_fail (app_info != NULL, 0); + g_strstrip (key); + g_strstrip (val); - instance = xdp_app_info_get_instance (app_info); + if (!strncmp (key, "NSpid", strlen ("NSpid"))) + { + r = parse_status_field_nspid (val, pid_out); + have_pid = r > -1; + } + else if (!strncmp (key, "Uid", strlen ("Uid"))) + { + r = parse_status_field_uid (val, uid_out); + have_uid = r > -1; + } - if (instance == NULL) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Could not find instance-id in process's /.flatpak-info"); - return 0; - } + if (r < 0) + g_warning ("Failed to parse 'status::%s': %s", + key, g_strerror (-r)); - path = g_build_filename (g_get_user_runtime_dir (), - ".flatpak", - instance, - "bwrapinfo.json", - NULL); + } while (r == 0 && (!have_uid || !have_pid)); - if (!g_file_get_contents (path, &data, &len, error)) - return 0; + fclose (f); - parser = json_parser_new (); - if (!json_parser_load_from_data (parser, data, len, error)) - { - g_prefix_error (error, "Could not parse '%s': ", path); - return 0; - } + if (r != 0) + return r; + else if (!have_uid || !have_pid) + return -ENXIO; /* ENOENT for the fields */ - root = json_node_ref (json_parser_get_root (parser)); - if (!root) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Could not parse '%s': empty file", path); - return 0; - } + return 0; +} - if (!JSON_NODE_HOLDS_OBJECT (root)) +static inline gboolean +find_pid (pid_t *pids, + guint n_pids, + pid_t want, + guint *idx) +{ + for (guint i = 0; i < n_pids; i++) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Could not parse '%s': invalid structure", path); - return 0; + if (pids[i] == want) + { + *idx = i; + return TRUE; + } } - return g_steal_pointer (&root); + return FALSE; } -static ino_t -xdp_app_info_get_pid_namespace (JsonNode *root, - GError **error) +gboolean +xdp_map_pids_full (DIR *proc, + ino_t pidns, + pid_t *pids, + guint n_pids, + uid_t target_uid, + GError **error) { - JsonNode *node; - JsonObject *cpo; - gint64 nsid; + pid_t *res = NULL; + struct dirent *de; + guint count = 0; - /* xdp_app_info_load_bwrap_info assures root is of type object */ - cpo = json_node_get_object (root); - node = json_object_get_member (cpo, "pid-namespace"); + res = g_alloca (sizeof (pid_t) * n_pids); + memset (res, 0, sizeof (pid_t) * n_pids); - if (node == NULL || !JSON_NODE_HOLDS_VALUE (node)) + while ((de = readdir (proc)) != NULL) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "pid-namespace missing"); - return 0; - } - - nsid = json_node_get_int (node); - return (ino_t) nsid; -} + g_autofd int pid_fd = -1; + pid_t outside = 0; + pid_t inside = 0; + uid_t uid = 0; + guint idx; + ino_t ns = 0; + int r; -static pid_t -xdp_app_info_get_child_pid (JsonNode *root, - GError **error) -{ - JsonObject *cpo; - pid_t pid; + if (de->d_type != DT_DIR) + continue; - cpo = json_node_get_object (root); + pid_fd = openat (dirfd (proc), de->d_name, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + if (pid_fd == -1) + continue; - pid = json_object_get_int_member (cpo, "child-pid"); - if (pid == 0) - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "child-pid missing"); + if (!xdp_pidfd_get_namespace (pid_fd, &ns, NULL)) + continue; - return pid; -} + if (pidns != ns) + continue; -static gboolean -xdp_app_info_ensure_pidns (XdpAppInfo *app_info, - DIR *proc, - GError **error) -{ - g_autoptr(JsonNode) root = NULL; - g_autoptr(GMutexLocker) guard = NULL; - xdp_autofd int fd = -1; - pid_t pid; - ino_t ns; - int r; + r = parse_pid (de->d_name, &outside); + if (r < 0) + continue; - g_assert (app_info->kind == XDP_APP_INFO_KIND_FLATPAK); + r = parse_status_file (pid_fd, &inside, &uid); + if (r < 0) + continue; - guard = g_mutex_locker_new (&(app_info->u.flatpak.pidns_lock)); + if (!find_pid (pids, n_pids, inside, &idx)) + continue; - if (app_info->u.flatpak.pidns_id != 0) - return TRUE; + /* We got a match, let's make sure the real uids match as well */ + if (uid != target_uid) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + "Matching pid doesn't belong to the target user"); + return FALSE; + } - root = xdp_app_info_load_bwrap_info (app_info, error); - if (root == NULL) - return FALSE; + /* this handles the first occurrence, already identified by find_pid, + * as well as duplicate entries */ + for (guint i = idx; i < n_pids; i++) + { + if (pids[i] == inside) + { + res[idx] = outside; + count++; + } + } + } - /* newer versions of bubblewrap contain the namespace - * information directly, so we don' thave to go via the - * child-pid; if this fails, we fallback to the old way */ - ns = xdp_app_info_get_pid_namespace (root, NULL); - if (ns != 0) + if (count != n_pids) { - g_debug ("Using pid namespace info from bwrap info"); - app_info->u.flatpak.pidns_id = ns; - return TRUE; - } + g_autoptr(GString) str = NULL; - pid = xdp_app_info_get_child_pid (root, error); - if (pid == 0) - return FALSE; + str = g_string_new ("Process ids could not be found: "); - fd = open_pid_fd (dirfd (proc), pid, error); - if (fd == -1) - return FALSE; + for (guint i = 0; i < n_pids; i++) + if (res[i] == 0) + g_string_append_printf (str, "%d, ", (guint32) pids[i]); + + g_string_truncate (str, str->len - 2); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, str->str); - r = lookup_ns_from_pid_fd (fd, &ns); - if (r < 0) - { - int code = g_io_error_from_errno (-r); - g_set_error (error, G_IO_ERROR, code, - "Could not query /proc/%u/ns/pid: %s", - (guint) pid, g_strerror (-r)); return FALSE; } - app_info->u.flatpak.pidns_id = ns; + memcpy (pids, res, sizeof (pid_t) * n_pids); return TRUE; } -/* This is the trunk for xdp_app_info_map_pids()/xdp_app_info_map_tids() */ static gboolean -app_info_map_pids (XdpAppInfo *app_info, - const char *proc_dir, - pid_t *pids, - guint n_pids, - GError **error) +map_pids_proc (ino_t pidns, + pid_t *pids, + guint n_pids, + const char *proc_dir, + GError **error) { gboolean ok; DIR *proc; uid_t uid; - ino_t ns; - g_return_val_if_fail (app_info != NULL, FALSE); + g_return_val_if_fail (pidns > 0, FALSE); g_return_val_if_fail (pids != NULL, FALSE); - if (app_info->kind == XDP_APP_INFO_KIND_HOST) - return TRUE; - - if (app_info->kind != XDP_APP_INFO_KIND_FLATPAK) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "Mapping pids is not supported."); - return FALSE; - } - proc = opendir (proc_dir); if (proc == NULL) { @@ -2207,230 +1169,33 @@ app_info_map_pids (XdpAppInfo *app_info, return FALSE; } - /* Make sure we know the pid namespace the app is running in */ - ok = xdp_app_info_ensure_pidns (app_info, proc, error); - if (!ok) - { - g_prefix_error (error, "Could not determine pid namespace: "); - goto out; - } - - /* we also make sure the real user id matches - * to the process owner we are trying to resolve - */ uid = getuid (); - ns = app_info->u.flatpak.pidns_id; - ok = map_pids (proc, ns, pids, n_pids, uid, error); - - out: + ok = xdp_map_pids_full (proc, pidns, pids, n_pids, uid, error); closedir (proc); - return ok; -} - - -gboolean -xdp_app_info_map_tids (XdpAppInfo *app_info, - pid_t owner_pid, - pid_t *tids, - guint n_tids, - GError **error) -{ - g_autofree char *proc_dir = g_strdup_printf ("/proc/%u/task", (guint) owner_pid); - return app_info_map_pids (app_info, proc_dir, tids, n_tids, error); -} - -gboolean -xdp_app_info_map_pids (XdpAppInfo *app_info, - pid_t *pids, - guint n_pids, - GError **error) -{ - return app_info_map_pids (app_info, "/proc", pids, n_pids, error); -} - -gboolean -xdp_app_info_pidfds_to_pids (XdpAppInfo *app_info, - const int *fds, - pid_t *pids, - gint count, - GError **error) -{ - gboolean ok = TRUE; - int fdinfo = -1; - - g_return_val_if_fail (app_info != NULL, FALSE); - g_return_val_if_fail (fds != NULL, FALSE); - g_return_val_if_fail (pids != NULL, FALSE); - - fdinfo = open_fdinfo_dir (error); - if (fdinfo == -1) - return FALSE; - - for (gint i = 0; i < count && ok; i++) - ok = pidfd_to_pid (fdinfo, fds[i], &pids[i], error); - - (void) close (fdinfo); return ok; } -void -cleanup_temp_file (void *p) -{ - void **pp = (void **)p; - - if (*pp) - remove (*pp); - g_free (*pp); -} - -#define ICON_VALIDATOR_GROUP "Icon Validator" - gboolean -xdp_validate_serialized_icon (GVariant *v, - gboolean bytes_only, - char **out_format, - char **out_size) +xdp_map_pids (ino_t pidns, + pid_t *pids, + guint n_pids, + GError **error) { - g_autoptr(GIcon) icon = NULL; - GBytes *bytes; - __attribute__((cleanup(cleanup_temp_file))) char *name = NULL; - xdp_autofd int fd = -1; - g_autoptr(GOutputStream) stream = NULL; - int status; - g_autofree char *format = NULL; - g_autofree char *stdoutlog = NULL; - g_autofree char *stderrlog = NULL; - g_autoptr(GError) error = NULL; - const char *icon_validator = LIBEXECDIR "/xdg-desktop-portal-validate-icon"; - const char *args[6]; - /* same allowed formats as Flatpak */ - const char *allowed_icon_formats[] = { "png", "jpeg", "svg", NULL }; - int size; - const char *MAX_ICON_SIZE = "512"; - gconstpointer bytes_data; - gsize bytes_len; - g_autoptr(GKeyFile) key_file = NULL; - - icon = g_icon_deserialize (v); - if (!icon) - { - g_warning ("Icon deserialization failed"); - return FALSE; - } - - if (!bytes_only && G_IS_THEMED_ICON (icon)) - { - g_autofree char *a = g_strjoinv (" ", (char **)g_themed_icon_get_names (G_THEMED_ICON (icon))); - g_debug ("Icon validation: themed icon (%s) is ok", a); - return TRUE; - } - - if (!G_IS_BYTES_ICON (icon)) - { - g_warning ("Unexpected icon type: %s", G_OBJECT_TYPE_NAME (icon)); - return FALSE; - } - - if (!g_file_test (icon_validator, G_FILE_TEST_EXISTS)) - { - g_warning ("Icon validation: %s not found, rejecting icon by default.", icon_validator); - return FALSE; - } - - bytes = g_bytes_icon_get_bytes (G_BYTES_ICON (icon)); - fd = g_file_open_tmp ("iconXXXXXX", &name, &error); - if (fd == -1) - { - g_warning ("Icon validation: %s", error->message); - return FALSE; - } - - stream = g_unix_output_stream_new (fd, FALSE); - - /* Use write_all() instead of write_bytes() so we don't have to worry about - * partial writes (https://gitlab.gnome.org/GNOME/glib/-/issues/570). - */ - bytes_data = g_bytes_get_data (bytes, &bytes_len); - if (!g_output_stream_write_all (stream, bytes_data, bytes_len, NULL, NULL, &error)) - { - g_warning ("Icon validation: %s", error->message); - return FALSE; - } - - if (!g_output_stream_close (stream, NULL, &error)) - { - g_warning ("Icon validation: %s", error->message); - return FALSE; - } - - args[0] = icon_validator; - args[1] = "--sandbox"; - args[2] = MAX_ICON_SIZE; - args[3] = MAX_ICON_SIZE; - args[4] = name; - args[5] = NULL; - - if (!g_spawn_sync (NULL, (char **)args, NULL, 0, NULL, NULL, &stdoutlog, &stderrlog, &status, &error)) - { - g_warning ("Icon validation: %s", error->message); - g_warning ("stderr:\n%s\n", stderrlog); - return FALSE; - } - - if (!g_spawn_check_exit_status (status, &error)) - { - g_warning ("Icon validation: %s", error->message); - return FALSE; - } - - key_file = g_key_file_new (); - if (!g_key_file_load_from_data (key_file, stdoutlog, -1, G_KEY_FILE_NONE, &error)) - { - g_warning ("Icon validation: %s", error->message); - return FALSE; - } - if (!(format = g_key_file_get_string (key_file, ICON_VALIDATOR_GROUP, "format", &error)) || - !g_strv_contains (allowed_icon_formats, format)) - { - g_warning ("Icon validation: %s", error ? error->message : "not allowed format"); - return FALSE; - } - if (!(size = g_key_file_get_integer (key_file, ICON_VALIDATOR_GROUP, "width", &error))) - { - g_warning ("Icon validation: %s", error->message); - return FALSE; - } - - if (out_format) - *out_format = g_steal_pointer (&format); - if (out_size) - *out_size = g_strdup_printf ("%d", size); - - return TRUE; + return map_pids_proc (pidns, pids, n_pids, "/proc", error); } gboolean -xdp_variant_contains_key (GVariant *dictionary, - const char *key) +xdp_map_tids (ino_t pidns, + pid_t owner_pid, + pid_t *tids, + guint n_tids, + GError **error) { - GVariantIter iter; - - g_variant_iter_init (&iter, dictionary); - while (TRUE) - { - g_autoptr(GVariant) entry = NULL; - g_autoptr(GVariant) entry_key = NULL; - - entry = g_variant_iter_next_value (&iter); - if (!entry) - break; + g_autofree char *proc_dir = NULL; - entry_key = g_variant_get_child_value (entry, 0); - if (g_strcmp0 (g_variant_get_string (entry_key, NULL), key) == 0) - return TRUE; - } + proc_dir = g_strdup_printf ("/proc/%u/task", (guint) owner_pid); - return FALSE; + return map_pids_proc (pidns, tids, n_tids, proc_dir, error); } diff --git a/src/xdp-utils.h b/src/xdp-utils.h index c92eea0..59a18d6 100644 --- a/src/xdp-utils.h +++ b/src/xdp-utils.h @@ -1,10 +1,12 @@ /* * Copyright © 2014, 2016 Red Hat, Inc * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -30,29 +32,10 @@ #include #include "glib-backports.h" +#include "xdp-sealed-fd.h" #define DESKTOP_PORTAL_OBJECT_PATH "/org/freedesktop/portal/desktop" -#define FLATPAK_METADATA_GROUP_APPLICATION "Application" -#define FLATPAK_METADATA_KEY_NAME "name" -#define FLATPAK_METADATA_GROUP_INSTANCE "Instance" -#define FLATPAK_METADATA_KEY_APP_PATH "app-path" -#define FLATPAK_METADATA_KEY_ORIGINAL_APP_PATH "original-app-path" -#define FLATPAK_METADATA_KEY_RUNTIME_PATH "runtime-path" -#define FLATPAK_METADATA_KEY_INSTANCE_ID "instance-id" - -#define SNAP_METADATA_GROUP_INFO "Snap Info" -#define SNAP_METADATA_KEY_INSTANCE_NAME "InstanceName" -#define SNAP_METADATA_KEY_DESKTOP_FILE "DesktopFile" -#define SNAP_METADATA_KEY_NETWORK "HasNetworkStatus" - -typedef enum -{ - XDP_APP_INFO_KIND_HOST = 0, - XDP_APP_INFO_KIND_FLATPAK = 1, - XDP_APP_INFO_KIND_SNAP = 2, -} XdpAppInfoKind; - gint xdp_mkstempat (int dir_fd, gchar *tmpl, int flags, @@ -62,64 +45,28 @@ gboolean xdp_is_valid_app_id (const char *string); char *xdp_get_app_id_from_desktop_id (const char *desktop_id); -gboolean xdp_validate_serialized_icon (GVariant *v, - gboolean bytes_only, - char **out_format, - char **out_size); +typedef enum +{ + XDP_ICON_TYPE_DESKTOP, + XDP_ICON_TYPE_NOTIFICATION, +} XdpIconType; -typedef void (*XdpPeerDiedCallback) (const char *name); +gboolean xdp_validate_icon (XdpSealedFd *icon, + XdpIconType icon_type, + char **out_format, + char **out_size); + +gboolean xdp_validate_sound (XdpSealedFd *sound); -typedef struct _XdpAppInfo XdpAppInfo; +typedef void (*XdpPeerDiedCallback) (const char *name); typedef int XdpFd; G_DEFINE_AUTO_CLEANUP_FREE_FUNC(XdpFd, close, -1) -XdpAppInfo *xdp_app_info_ref (XdpAppInfo *app_info); -void xdp_app_info_unref (XdpAppInfo *app_info); -const char *xdp_app_info_get_id (XdpAppInfo *app_info); -char * xdp_app_info_get_instance (XdpAppInfo *app_info); -gboolean xdp_app_info_is_host (XdpAppInfo *app_info); -XdpAppInfoKind xdp_app_info_get_kind (XdpAppInfo *app_info); -gboolean xdp_app_info_supports_opath (XdpAppInfo *app_info); -char * xdp_app_info_remap_path (XdpAppInfo *app_info, - const char *path); -gboolean xdp_app_info_map_pids (XdpAppInfo *app_info, - pid_t *pids, - guint n_pids, - GError **error); -gboolean xdp_app_info_map_tids (XdpAppInfo *app_info, - pid_t owner_pid, - pid_t *tids, - guint n_tids, - GError **error); -gboolean xdp_app_info_pidfds_to_pids (XdpAppInfo *app_info, - const int *fds, - pid_t *pids, - gint count, - GError **error); -char * xdp_app_info_get_path_for_fd (XdpAppInfo *app_info, - int fd, - int require_st_mode, - struct stat *st_buf, - gboolean *writable_out, - GError **error); -gboolean xdp_app_info_has_network (XdpAppInfo *app_info); -XdpAppInfo *xdp_get_app_info_from_pid (pid_t pid, - GError **error); -GAppInfo * xdp_app_info_load_app_info (XdpAppInfo *app_info); -char ** xdp_app_info_rewrite_commandline (XdpAppInfo *app_info, - const char *const *commandline, - gboolean quote_escape); -char *xdp_app_info_get_tryexec_path (XdpAppInfo *app_info); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC(XdpAppInfo, xdp_app_info_unref) - -void xdp_set_documents_mountpoint (const char *path); -char *xdp_get_alternate_document_path (const char *path, const char *app_id); - -XdpAppInfo *xdp_invocation_lookup_app_info_sync (GDBusMethodInvocation *invocation, - GCancellable *cancellable, - GError **error); +void xdp_set_documents_mountpoint (const char *path); +const char * xdp_get_documents_mountpoint (void); +char * xdp_get_alternate_document_path (const char *path, const char *app_id); + void xdp_connection_track_name_owners (GDBusConnection *connection, XdpPeerDiedCallback peer_died_cb); @@ -134,7 +81,7 @@ typedef struct { gboolean xdp_filter_options (GVariant *options_in, GVariantBuilder *options_out, - XdpOptionKey *supported_options, + const XdpOptionKey *supported_options, int n_supported_options, GError **error); @@ -152,59 +99,58 @@ typedef enum { GQuark xdg_desktop_portal_error_quark (void); -static inline int -xdp_steal_fd (int *fdp) -{ - int fd = *fdp; - *fdp = -1; - return fd; -} - -static inline void -xdp_close_fd (int *fdp) -{ - int errsv; - - g_assert (fdp); - - int fd = xdp_steal_fd (fdp); - if (fd >= 0) - { - errsv = errno; - if (close (fd) < 0) - g_assert (errno != EBADF); - errno = errsv; - } -} - -#define xdp_autofd __attribute__((cleanup(xdp_close_fd))) - #define XDP_AUTOLOCK(name) \ g_autoptr(GMutexLocker) G_PASTE (name ## locker, __LINE__) = \ g_mutex_locker_new (&G_LOCK_NAME (name)); \ (void) G_PASTE (name ## locker, __LINE__); -char * xdp_quote_argv (const char *argv[]); -gboolean xdp_spawn (GFile *dir, - char **output, - GSubprocessFlags flags, - GError **error, - const gchar *argv0, - va_list ap); -gboolean xdp_spawnv (GFile *dir, - char **output, - GSubprocessFlags flags, - GError **error, - const gchar * const *argv); +char * xdp_maybe_quote (const char *arg, + gboolean quote_escape); +char * xdp_maybe_quote_argv (const char *argv[], + gboolean quote_escape); + +char * xdp_spawn (GError **error, + const char *argv0, + ...) G_GNUC_NULL_TERMINATED; +char * xdp_spawn_full (const char * const *argv, + int source_fd, + int target_fd, + GError **error); char * xdp_canonicalize_filename (const char *path); gboolean xdp_has_path_prefix (const char *str, const char *prefix); -/* exposed for the benefit of tests */ -int _xdp_parse_cgroup_file (FILE *f, - gboolean *is_snap); -#ifdef HAVE_LIBSYSTEMD -char *_xdp_parse_app_id_from_unit_name (const char *unit); -#endif +pid_t xdp_pidfd_to_pid (int pidfd, + GError **error); + +gboolean xdp_pidfds_to_pids (const int *pidfds, + pid_t *pids, + gint count, + GError **error); + +gboolean xdp_pidfd_get_namespace (int pidfd, + ino_t *ns, + GError **error); + +gboolean xdp_map_pids_full (DIR *proc, + ino_t pidns, + pid_t *pids, + guint n_pids, + uid_t target_uid, + GError **error); + +gboolean xdp_map_pids (ino_t pidns, + pid_t *pids, + guint n_pids, + GError **error); + +gboolean xdp_map_tids (ino_t pidns, + pid_t owner_pid, + pid_t *tids, + guint n_tids, + GError **error); + +#define XDP_EXPORT_TEST XDP_EXPORT +#define XDP_EXPORT __attribute__((visibility("default"))) extern diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..20f4718 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,105 @@ +xdg-desktop-portal test suite +============================= + +## Unit tests + +This directory contains a number of unit tests. The tests are written in C and +are using the glib testing framework (https://docs.gtk.org/glib/testing.html). + +The files follow the pattern `test-$NAME.c` and are compiled by meson. The tests +can be run with `meson test --suite unit`. + +## Integration tests + +The integration tests usually test a specific portal in a fully integrated +environment. The tests are written in python using the pytest framework. + +The files follow the pattern `test_$NAME.py`. The tests can be run with +`meson test --suite integration` or with `run-test.sh` in the source directory. + +The environment is being set up by fixtures in `conftest.py` which can be +overwritten or parameterized by the tests themselves. There are a bunch of +convenient functions and classes in `__init__.py`. The portal backends are +implemented using dbusmock templates in the `templates` directory. + +### Environment + +Some environment variables need to be set for the integration tests to function +properly and the harness will refuse to launch if they are not set. If the +harness is executed by meson or run-test.sh, they will be set automatically. + +* `XDG_DESKTOP_PORTAL_PATH`: The path to the xdg-desktop-portal binary + +* `XDG_PERMISSION_STORE_PATH`: The path to the xdg-permission-store binary + +* `XDG_DOCUMENT_PORTAL_PATH`: The path to the xdg-document-portal binary + +* `XDP_VALIDATE_ICON`: The path to the xdg-desktop-portal-validate-icon binary + +* `XDP_VALIDATE_SOUND`: The path to the xdg-desktop-portal-validate-sound binary + +* `XDP_VALIDATE_AUTO`: If set, automatically discovers the icon and sound + validators (only useful for installed tests) instead of using + `XDP_VALIDATE_ICON` and `XDP_VALIDATE_SOUND`. + +Some optional environment variables that can be set to influence how the test +harness behaves. + +* `XDP_TEST_IN_CI`: If set (to any value), some unreliable tests might get + skipped and some tests might run less iterations or otherwise test less + thoroughly. + Set this for automated QA testing, leave it unset during development. + +* `XDP_TEST_RUN_LONG`: If set (to any value), some tests will run more + iterations or otherwise test more thoroughly + +* `FLATPAK_BWRAP`: Path to the **bwrap**(1) executable + (default: discovered at build-time) + +* `XDP_VALIDATE_ICON_INSECURE`: If set (to any value), x-d-p doesn't + sandbox the icon validator using **bwrap**(1), even if sandboxed + validation was enabled at compile time. + This can be used to run build-time tests in a chroot or unprivileged + container environment, where **bwrap**(1) normally can't work. + It should never be set on a production system that will be validating + untrusted icons! + +* `XDP_VALIDATE_SOUND_INSECURE`: Same as `XDP_VALIDATE_ICON_INSECURE`, + but for sounds + +Some optional environment variables that can be set to help with debugging. + +* `XDP_DBUS_MONITOR`: If set, starts dbus-monitor on the test dbus server + +* `XDP_DBUS_TIMEOUT`: Maximum timeout for dbus calls in ms (default: 5s) + +* `XDG_DESKTOP_PORTAL_WAIT_FOR_DEBUGGER`: Makes xdg-desktop-portal wait for + a debugger to attach by raising SIGSTOP + +* `XDG_DOCUMENT_PORTAL_WAIT_FOR_DEBUGGER`: Makes xdg-document-portal wait + for a debugger to attach by raising SIGSTOP + +* `XDG_PERMISSION_STORE_WAIT_FOR_DEBUGGER`: Makes xdg-permission-store wait + for a debugger to attach by raising SIGSTOP + +Internal environment variables the tests use via pytest fixtures to set up the +environment they need. + +* `XDG_DESKTOP_PORTAL_TEST_APP_ID`: If set, the portal will use a host + XdpAppInfo with the app id set to the variable. This is used to get a + predictable app id for tests. + +* `XDG_DESKTOP_PORTAL_TEST_USB_QUERIES`: The USB queries for the USB device + portal testing + +### Adding new tests + +Make sure the required portals are listed in +`xdg_desktop_portal_dir_default_files` in `conftest.py`. + +Add a `test_${name}.py` file to this directory and add the file to +`meson.build`. + +If the portal that is being tested requires a backend implementation, add +it to the `templates` directory and add the file to `meson.build`. See the +dbusmock documentation for details on those templates. diff --git a/tests/__init__.py b/tests/__init__.py index 93b2eb2..43d77a5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -3,48 +3,221 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # This file is formatted with Python Black -# - -# Shared setup for portal tests. To test a portal, subclass TestPortal with -# your portal's name (e.g. TestEmail). This will auto-fill your portal -# name into some of the functions. -# -# Make sure the portal is listed in tests/portals/test.portal and you have a -# dbusmock template for the impl.portal of your portal in tests/templates. See -# the dbusmock documentation for details on those templates. -# -# Environment variables: -# G_TEST_BUILDDIR: override the path to the tests/ build -# directory (default: $PWD) -# LIBEXECDIR: run xdg-desktop-portal from that dir -# XDP_DBUS_MONITOR: if set, starts dbus_monitor on the custom bus, useful -# for debugging from dbus.mainloop.glib import DBusGMainLoop -from gi.repository import GLib +from gi.repository import GLib, Gio from itertools import count -from typing import Any, Dict, Optional, NamedTuple -from pathlib import Path +from typing import Any, Dict, Optional, NamedTuple, Callable, List +import os import dbus import dbus.proxies import dbusmock -import fcntl import logging -import os import subprocess -import time + DBusGMainLoop(set_as_default=True) # Anything that takes longer than 5s needs to fail -MAX_TIMEOUT = 5000 +DBUS_TIMEOUT = int(os.environ.get("XDP_DBUS_TIMEOUT", "5000")) _counter = count() ASV = Dict[str, Any] -logger = logging.getLogger("tests") + +def init_logger(name: str) -> logging.Logger: + """ + Common logging setup for tests. Use as: + + >>> import tests as xdp + >>> logger = xdp.init_logger(__name__) + >>> logger.debug("foo") + + """ + logging.basicConfig( + format="%(levelname).1s|%(name)s: %(message)s", level=logging.DEBUG + ) + logger = logging.getLogger(f"xdp.{name}") + logger.setLevel(logging.DEBUG) + return logger + + +logger = init_logger("utils") + + +def is_in_ci() -> bool: + return os.environ.get("XDP_TEST_IN_CI") is not None + + +def is_in_container() -> bool: + return is_in_ci() or ( + "container" in os.environ + and (os.environ["container"] == "docker" or os.environ["container"] == "podman") + ) + + +def run_long_tests() -> bool: + return os.environ.get("XDP_TEST_RUN_LONG") is not None + + +def check_program_success(cmd) -> bool: + proc = subprocess.Popen( + cmd, stdout=None, stderr=None, shell=True, universal_newlines=True + ) + _ = proc.communicate() + return proc.returncode == 0 + + +class FuseNotSupportedException(Exception): + pass + + +def ensure_fuse_supported() -> None: + if not check_program_success("fusermount3 --version"): + raise FuseNotSupportedException("no fusermount3") + + if not check_program_success( + "capsh --print | grep -q 'Bounding set.*[^a-z]cap_sys_admin'" + ): + raise FuseNotSupportedException( + "No cap_sys_admin in bounding set, can't use FUSE" + ) + + if not check_program_success("[ -w /dev/fuse ]"): + raise FuseNotSupportedException("no write access to /dev/fuse") + + if not check_program_success("[ -e /etc/mtab ]"): + raise FuseNotSupportedException("no /etc/mtab") + + +def wait(ms: int): + """ + Waits for the specified amount of milliseconds. + """ + mainloop = GLib.MainLoop() + GLib.timeout_add(ms, mainloop.quit) + mainloop.run() + + +def wait_for(fn: Callable[[], bool]): + """ + Waits and dispatches to mainloop until the function fn returns true. This is + useful in combination with a lambda which captures a variable: + + my_var = False + def callback(): + my_var = True + do_something_later(callback) + xdp.wait_for(lambda: my_var) + """ + mainloop = GLib.MainLoop() + while not fn(): + GLib.timeout_add(50, mainloop.quit) + mainloop.run() + + +def get_permission_store_iface(bus: dbus.Bus) -> dbus.Interface: + """ + Returns the dbus interface of the xdg-permission-store. + """ + obj = bus.get_object( + "org.freedesktop.impl.portal.PermissionStore", + "/org/freedesktop/impl/portal/PermissionStore", + ) + return dbus.Interface(obj, "org.freedesktop.impl.portal.PermissionStore") + + +def get_document_portal_iface(bus: dbus.Bus) -> dbus.Interface: + """ + Returns the dbus interface of the xdg-document-portal. + """ + obj = bus.get_object( + "org.freedesktop.portal.Documents", + "/org/freedesktop/portal/documents", + ) + return dbus.Interface(obj, "org.freedesktop.portal.Documents") + + +def get_mock_iface(bus: dbus.Bus, bus_name: Optional[str] = None) -> dbus.Interface: + """ + Returns the mock interface of the xdg-desktop-portal. + """ + if not bus_name: + bus_name = "org.freedesktop.impl.portal.Test" + + obj = bus.get_object(bus_name, "/org/freedesktop/portal/desktop") + return dbus.Interface(obj, dbusmock.MOCK_IFACE) + + +def portal_interface_name(portal_name: str, domain: Optional[str] = None) -> str: + """ + Returns the fully qualified interface for a portal name. + """ + if domain: + return f"org.freedesktop.{domain}.portal.{portal_name}" + else: + return f"org.freedesktop.portal.{portal_name}" + + +def get_portal_iface( + bus: dbus.Bus, name: str, domain: Optional[str] = None +) -> dbus.Interface: + """ + Returns the dbus interface for a portal name. + """ + name = portal_interface_name(name, domain) + return get_iface(bus, name) + + +def get_iface(bus: dbus.Bus, name: str) -> dbus.Interface: + """ + Returns a named interface of the main portal object. + """ + try: + ifaces = bus._xdp_portal_ifaces + except AttributeError: + ifaces = bus._xdp_portal_ifaces = {} + + try: + intf = ifaces[name] + except KeyError: + intf = dbus.Interface(get_xdp_dbus_object(bus), name) + assert intf + ifaces[name] = intf + return intf + + +def get_xdp_dbus_object(bus: dbus.Bus) -> dbus.proxies.ProxyObject: + """ + Returns the main portal object. + """ + try: + obj = getattr(bus, "_xdp_dbus_object") + except AttributeError: + obj = bus.get_object( + "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop" + ) + assert obj + bus._xdp_dbus_object = obj + return obj + + +def check_version(bus: dbus.Bus, portal_name: str, expected_version: int): + """ + Checks that the portal_name portal version is equal to expected_version. + """ + properties_intf = dbus.Interface( + get_xdp_dbus_object(bus), "org.freedesktop.DBus.Properties" + ) + portal_iface_name = portal_interface_name(portal_name) + try: + portal_version = properties_intf.Get(portal_iface_name, "version") + assert int(portal_version) == expected_version + except dbus.exceptions.DBusException as e: + logger.critical(e) + assert e is None, str(e) class Response(NamedTuple): @@ -76,7 +249,7 @@ def __init__(self, bus: dbus.Bus, objpath: str): # GLib makes assertions in callbacks impossible, so we wrap all # callbacks into a try: except and store the error on the request to # be raised later when we're back in the main context - self.error = None + self.error: Optional[Exception] = None self._mainloop: Optional[GLib.MainLoop] = None self._impl_closed = False @@ -117,7 +290,7 @@ def cb_impl_closed_by_portal(handle) -> None: signal_match = self._bus.add_signal_receiver( cb_impl_closed_by_portal, f"{self._closable}Closed", - dbus_interface="org.freedesktop.impl.portal.Test", + dbus_interface="org.freedesktop.impl.portal.Mock", ) logger.debug(f"Closing {self._closable} {self.objpath}") @@ -127,7 +300,7 @@ def schedule_close(self, timeout_ms=300): """ Schedule an automatic Close() on the given timeout in milliseconds. """ - assert 0 < timeout_ms < MAX_TIMEOUT + assert 0 < timeout_ms < DBUS_TIMEOUT GLib.timeout_add(timeout_ms, self.close) @@ -219,7 +392,7 @@ def call(self, methodname: str, **kwargs) -> Optional[Response]: # Anything that takes longer than 5s needs to fail self._mainloop = GLib.MainLoop() - GLib.timeout_add(MAX_TIMEOUT, self._mainloop.quit) + GLib.timeout_add(DBUS_TIMEOUT, self._mainloop.quit) method = getattr(self.interface, methodname) assert method @@ -327,186 +500,121 @@ def from_response(cls, bus: dbus.Bus, response: Response) -> "Session": return cls(bus, response.results["session_handle"]) -class PortalMock: +class GDBusIfaceSignal: """ - Parent class for portal tests. + Helper class which represents a connected signal on a GDBusIface and can be + used to disconnect from the signal. """ - def __init__(self, session_bus, portal_name: str): - self.bus = session_bus - self.portal_name = portal_name - self.p_mock = None - self.xdp = None - self.portal_interfaces: Dict[str, dbus.Interface] = {} - self.dbus_monitor = None - - @property - def interface_name(self) -> str: - return f"org.freedesktop.portal.{self.portal_name}" - - @property - def dbus_con(self): - return self.bus.dbus_con + def __init__(self, signal_id: int, proxy: Gio.DBusProxy): + self.signal_id = signal_id + self.proxy = proxy - def start_impl_portal(self, params=None, portal=None): + def disconnect(self): """ - Start the impl.portal for the given portal name. If missing, - the portal name is derived from the class name of the test, e.g. - ``TestFoo`` will start ``org.freedesktop.impl.portal.Foo``. + Disconnects the signal """ - portal = portal or self.portal_name - self.p_mock, self.obj_portal = self.bus.spawn_server_template( - template=f"tests/templates/{portal.lower()}.py", - parameters=params, - stdout=subprocess.PIPE, - ) - flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) - fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) - self.mock_interface = dbus.Interface(self.obj_portal, dbusmock.MOCK_IFACE) + self.proxy.disconnect(self.signal_id) - self.start_dbus_monitor() - def add_template(self, portal, params: Dict[str, Any] = {}): +class GDBusIface: + """ + Helper class for calling dbus interfaces with complex arguments. + Usually you want to use python-dbus on the dbus_con fixture with + get_portal_iface , get_mock_iface or get_iface. This is convenient but + might not be sufficient for complex arguments or for asynchronously calling + a method. + """ + + def __init__(self, bus: str, obj: str, iface: str): """ - Add an additional template to the portal object + Creates a GDBusIface for a specific bus, object and interface on the + session bus. """ - - self.obj_portal.AddTemplate( - f"tests/templates/{portal.lower()}.py", - dbus.Dictionary(params, signature="sv"), - dbus_interface=dbusmock.MOCK_IFACE, + address = Gio.dbus_address_get_for_bus_sync(Gio.BusType.SESSION, None) + session_bus = Gio.DBusConnection.new_for_address_sync( + address, + Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT + | Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION, + None, + None, + ) + assert session_bus + self._proxy = Gio.DBusProxy.new_sync( + session_bus, + Gio.DBusProxyFlags.NONE, + None, + bus, + obj, + iface, + None, ) - def start_xdp(self): + def _call( + self, method_name: str, args_variant: GLib.Variant, fds: List[int] = [] + ) -> GLib.Variant: """ - Start the xdg-desktop-portal process + Calls a method synchronously with the arguments passed in args_variant, + passing the file descriptors specified in fds. + Returns the result of the dbus call. """ + fdlist = Gio.UnixFDList.new() + for fd in fds: + fdlist.append(fd) + + return self._proxy.call_with_unix_fd_list_sync( + method_name, + args_variant, + 0, + -1, + fdlist, + None, + ) - # This roughly resembles test-portals.c and glib's test behavior - # but preferences in-tree testing by running pytest in meson's - # project_build_root - libexecdir = os.getenv("LIBEXECDIR") - if libexecdir: - xdp_path = Path(libexecdir) / "xdg-desktop-portal" - else: - xdp_path = ( - Path(os.getenv("G_TEST_BUILDDIR") or "tests") - / ".." - / "src" - / "xdg-desktop-portal" - ) - - if not xdp_path.exists(): - raise FileNotFoundError( - f"{xdp_path} does not exist, try running from meson build dir or setting G_TEST_BUILDDIR" - ) - - portal_dir = Path(os.getenv("G_TEST_BUILDDIR") or "tests") / "portals" / "test" - if not portal_dir.exists(): - raise FileNotFoundError( - f"{portal_dir} does not exist, try running from meson build dir or setting G_TEST_SRCDIR" - ) - - argv = [xdp_path] - env = os.environ.copy() - env["G_DEBUG"] = "fatal-criticals" - env["XDG_DESKTOP_PORTAL_DIR"] = portal_dir - env["XDG_CURRENT_DESKTOP"] = "test" - - xdp = subprocess.Popen(argv, env=env) - - for _ in range(50): - if self.bus.dbus_con.name_has_owner("org.freedesktop.portal.Desktop"): - break - time.sleep(0.1) - else: - assert ( - False - ), "Timeout while waiting for xdg-desktop-portal to claim the bus" - - self.xdp = xdp - - def start_dbus_monitor(self): - if not os.getenv("XDP_DBUS_MONITOR"): - return - - argv = ["dbus-monitor", "--session"] - self.dbus_monitor = subprocess.Popen(argv) - - def tearDown(self): - if self.dbus_monitor: - self.dbus_monitor.terminate() - self.dbus_monitor.wait() - - if self.xdp: - self.xdp.terminate() - self.xdp.wait() - - if self.p_mock: - if self.p_mock.stdout: - out = (self.p_mock.stdout.read() or b"").decode("utf-8") - if out: - print(out) - self.p_mock.stdout.close() - self.p_mock.terminate() - self.p_mock.wait() - - def get_xdp_dbus_object(self) -> dbus.proxies.ProxyObject: + def _call_async( + self, + method_name: str, + args_variant: GLib.Variant, + fds: List[int] = [], + cb: Optional[Callable[[GLib.Variant], None]] = None, + ) -> None: """ - Return the object that is the org.freedesktop.portal.Desktop proxy + Calls a method asynchronously with the arguments passed in args_variant, + passing the file descriptors specified in fds. + Invokes the callback cb when the call finished. """ - try: - return self._xdp_dbus_object - except AttributeError: - obj = self.bus.dbus_con.get_object( - "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop" - ) - # Useful for debugging: - # print(obj.Introspect(dbus_interface="org.freedesktop.DBus.Introspectable")) - assert obj - self._xdp_dbus_object: dbus.proxies.ProxyObject = obj - return self._xdp_dbus_object - - def get_dbus_interface(self, name=None) -> dbus.Interface: - """ - Return the interface with the given name. - - >>> my_portal_intf = self.get_dbus_interface() - >>> rd_portal_intf = self.get_dbus_interface("RemoteDesktop") - >>> dbus_intf = self.get_dbus_interface("org.freedesktop.DBus.Introspectable") + fdlist = Gio.UnixFDList.new() + for fd in fds: + fdlist.append(fd) + + def internal_cb(s, res, _): + res = s.call_finish(res) + if cb: + cb(res) + + self._proxy.call_with_unix_fd_list( + method_name, + args_variant, + 0, + -1, + fdlist, + None, + internal_cb, + None, + ) - For portals, it's enough to specify the portal name (e.g. "InputCapture"). - If no name is provided, guess from the test class name. + def connect_to_signal( + self, name: str, cb: Callable[[GLib.Variant], None] + ) -> GDBusIfaceSignal: """ - name = name or self.interface_name - if "." not in name: - name = f"org.freedesktop.portal.{name}" - - try: - intf = getattr(self, "portal_interfaces", {})[name] - except KeyError: - intf = dbus.Interface(self.get_xdp_dbus_object(), name) - assert intf - self.portal_interfaces[name] = intf - return intf - - def create_request(self, intf_name: Optional[str] = None) -> Request: - intf = self.get_dbus_interface(intf_name) - return Request(self.dbus_con, intf) - - def check_version(self, expected_version): + Connects to the dbus signal name to the callback cb. Returns an object + representing the connection which can be used to disconnect it again. """ - Helper function to check for a portal's version. Use as: - >>> class TestFoo(PortalMock): - ... def test_version(self): - ... self.check_version(2) - >>> - """ - properties_intf = self.get_dbus_interface("org.freedesktop.DBus.Properties") - try: - portal_version = properties_intf.Get(self.interface_name, "version") - assert int(portal_version) == expected_version - except dbus.exceptions.DBusException as e: - logger.critical(e) - assert e is None, str(e) + def internal_cb(proxy, sender_name, signal_name, parameters): + if signal_name != name: + return + cb(parameters) + + signal_id = self._proxy.connect("g-signal", internal_cb) + return GDBusIfaceSignal(signal_id, self._proxy) diff --git a/tests/account.c b/tests/account.c deleted file mode 100644 index 2d31779..0000000 --- a/tests/account.c +++ /dev/null @@ -1,333 +0,0 @@ -#include - -#include "account.h" - -#include - -extern char outdir[]; - -/* We use g_main_context_wakeup() and a boolean variable - * to make the test cases wait for async calls to return - * without a maze of callbacks. - * - * The tests communicate with the backend via a keyfile - * in a shared location. - */ -static int got_info = 0; - -static void -account_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - g_autoptr(GVariant) ret = NULL; - GKeyFile *keyfile = data; - gboolean res; - const char *s; - char *t; - int response; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - - ret = xdp_portal_get_user_information_finish (portal, result, &error); - if (response == 0) - { - g_assert_no_error (error); - - t = g_key_file_get_string (keyfile, "account", "id", NULL); - res = g_variant_lookup (ret, "id", "&s", &s); - g_assert (res == (t != NULL)); - if (t) g_assert_cmpstr (s, ==, t); - free (t); - - t = g_key_file_get_string (keyfile, "account", "name", NULL); - res = g_variant_lookup (ret, "name", "&s", &s); - g_assert (res == (t != NULL)); - if (t) g_assert_cmpstr (s, ==, t); - free (t); - - t = g_key_file_get_string (keyfile, "account", "image", NULL); - res = g_variant_lookup (ret, "image", "&s", &s); - g_assert (res == (t != NULL)); - if (t) g_assert_cmpstr (s, ==, t); - free (t); - } - else if (response == 1) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - else if (response == 2) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -static void -account_cb_fail (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - g_autoptr(GVariant) ret = NULL; - - ret = xdp_portal_get_user_information_finish (portal, result, &error); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); - - got_info++; - g_main_context_wakeup (NULL); -} - -/* some basic tests using libportal, and test that communication - * with the backend via keyfile works - */ -void -test_account_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "account", "id", "test"); - g_key_file_set_string (keyfile, "account", "name", "Donald Duck"); - g_key_file_set_string (keyfile, "account", "image", ""); - - g_key_file_set_string (keyfile, "backend", "reason", "test"); - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "account", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_get_user_information (portal, NULL, "test", 0, NULL, account_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* check that the reason argument makes it to the backend - */ -void -test_account_reason (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char *long_reason; - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "account", "id", "test"); - g_key_file_set_string (keyfile, "account", "name", "Donald Duck"); - - g_key_file_set_string (keyfile, "backend", "reason", "xx"); - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "account", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_get_user_information (portal, NULL, "xx", 0, NULL, account_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - got_info = 0; - xdp_portal_get_user_information (portal, NULL, "yy", 0, NULL, account_cb_fail, NULL); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - g_key_file_remove_key (keyfile, "backend", "reason", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - long_reason = "This reason is unreasonably long, it stretches over " - "more than twohundredfiftysix characters, which is really quite " - "long. Excessively so. The portal frontend will silently drop " - "reasons of this magnitude. If you can't express your reasons " - "concisely, you probably have no good reason in the first place " - "and are just waffling around."; - g_assert (g_utf8_strlen (long_reason, -1) > 256); - - got_info = 0; - xdp_portal_get_user_information (portal, NULL, long_reason, 0, NULL, account_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - -} - -/* test that everything works as expected when the - * backend takes some time to send its response, as - * is to be expected from a real backend that presents - * dialogs to the user. - */ -void -test_account_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - g_key_file_set_string (keyfile, "account", "id", "test"); - g_key_file_set_string (keyfile, "account", "name", "Donald Duck"); - g_key_file_set_string (keyfile, "backend", "reason", "xx"); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "account", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_get_user_information (portal, NULL, "xx", 0, NULL, account_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* Test that user cancellation works as expected. - * We simulate that the user cancels a hypothetical dialog, - * by telling the backend to return 1 as response code. - * And we check that we get the expected G_IO_ERROR_CANCELLED. - */ -void -test_account_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - g_key_file_set_string (keyfile, "account", "id", "test"); - g_key_file_set_string (keyfile, "account", "name", "Donald Duck"); - g_key_file_set_string (keyfile, "backend", "reason", "xx"); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "account", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_get_user_information (portal, NULL, "xx", 0, NULL, account_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -static gboolean -cancel_call (gpointer data) -{ - GCancellable *cancellable = data; - - g_debug ("cancel call"); - g_cancellable_cancel (cancellable); - - return G_SOURCE_REMOVE; -} - -/* Test that app-side cancellation works as expected. - * We cancel the cancellable while while the hypothetical - * dialog is up, and tell the backend that it should - * expect a Close call. We rely on the backend to - * verify that that call actually happened. - */ -void -test_account_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GCancellable) cancellable = NULL; - - keyfile = g_key_file_new (); - g_key_file_set_string (keyfile, "account", "id", "test"); - g_key_file_set_string (keyfile, "account", "name", "Donald Duck"); - g_key_file_set_string (keyfile, "backend", "reason", "xx"); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "account", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_get_user_information (portal, NULL, "xx", 0, cancellable, account_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* Test multiple requests in parallel */ -void -test_account_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "account", "id", "test"); - g_key_file_set_string (keyfile, "account", "name", "Donald Duck"); - g_key_file_set_string (keyfile, "account", "image", ""); - - g_key_file_set_string (keyfile, "backend", "reason", "test"); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "account", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_get_user_information (portal, NULL, "test", 0, NULL, account_cb, keyfile); - xdp_portal_get_user_information (portal, NULL, "test", 0, NULL, account_cb, keyfile); - xdp_portal_get_user_information (portal, NULL, "test", 0, NULL, account_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} - diff --git a/tests/account.h b/tests/account.h deleted file mode 100644 index b2f61f2..0000000 --- a/tests/account.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -void test_account_basic (void); -void test_account_delay (void); -void test_account_cancel (void); -void test_account_close (void); -void test_account_parallel (void); -void test_account_reason (void); diff --git a/tests/asan.suppression b/tests/asan.suppression new file mode 100644 index 0000000..ac1f115 --- /dev/null +++ b/tests/asan.suppression @@ -0,0 +1,8 @@ +# external (GIO?) +leak:g_dbus_message_new_from_blob +# Bugs in our code +# Take a look at them and try to figure out what's going on! +leak:permission_db_entry_set_app_permissions +leak:test_color_delay +leak:test_color_basic +leak:test_color_parallel diff --git a/tests/backend/access.c b/tests/backend/access.c deleted file mode 100644 index 0ba13c0..0000000 --- a/tests/backend/access.c +++ /dev/null @@ -1,171 +0,0 @@ -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "access.h" - -typedef struct { - XdpDbusImplAccess *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - char *title; - char *subtitle; - char *body; - GVariant *options; - guint timeout; -} AccessHandle; - -static void -access_handle_free (AccessHandle *handle) -{ - g_object_unref (handle->impl); - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - g_free (handle->title); - g_free (handle->subtitle); - g_free (handle->body); - if (handle->timeout) - g_source_remove (handle->timeout); - - g_free (handle); -} - -static gboolean -send_response (gpointer data) -{ - AccessHandle *handle = data; - GVariantBuilder opt_builder; - int response; - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - - if (handle->request->exported) - request_unexport (handle->request); - - g_debug ("send response %d", response); - - xdp_dbus_impl_access_complete_access_dialog (handle->impl, - handle->invocation, - response, - g_variant_builder_end (&opt_builder)); - - handle->timeout = 0; - - access_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation, - AccessHandle *handle) -{ - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - g_debug ("AccessDialog handling Close"); - xdp_dbus_impl_access_complete_access_dialog (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - access_handle_free (handle); - - return FALSE; -} - -static gboolean -handle_access_dialog (XdpDbusImplAccess *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - const char *arg_title, - const char *arg_subtitle, - const char *arg_body, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - int delay; - AccessHandle *handle; - g_autoptr(Request) request = NULL; - - g_debug ("Handling AccessDialog"); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "access", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new0 (AccessHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - handle->title = g_strdup (arg_title); - handle->subtitle = g_strdup (arg_subtitle); - handle->body = g_strdup (arg_body); - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -void -access_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_access_skeleton_new ()); - - g_signal_connect (helper, "handle-access-dialog", G_CALLBACK (handle_access_dialog), NULL); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/access.h b/tests/backend/access.h deleted file mode 100644 index fb18711..0000000 --- a/tests/backend/access.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void access_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/account.c b/tests/backend/account.c deleted file mode 100644 index 19f8ff1..0000000 --- a/tests/backend/account.c +++ /dev/null @@ -1,186 +0,0 @@ -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "account.h" - -typedef struct { - XdpDbusImplAccount *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - char *reason; - guint timeout; -} AccountDialogHandle; - -static void -account_dialog_handle_free (AccountDialogHandle *handle) -{ - g_object_unref (handle->impl); - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - g_free (handle->reason); - if (handle->timeout) - g_source_remove (handle->timeout); - - g_free (handle); -} - -static gboolean -send_response (gpointer data) -{ - AccountDialogHandle *handle = data; - GVariantBuilder opt_builder; - g_autofree char *reason = NULL; - g_autofree char *id = NULL; - g_autofree char *name = NULL; - g_autofree char *image = NULL; - g_autoptr(GError) error = NULL; - int response; - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - reason = g_key_file_get_string (handle->keyfile, "backend", "reason", &error); - id = g_key_file_get_string (handle->keyfile, "account", "id", NULL); - name = g_key_file_get_string (handle->keyfile, "account", "name", NULL); - image = g_key_file_get_string (handle->keyfile, "account", "image", NULL); - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - if (g_strcmp0 (handle->reason, reason) != 0) - { - g_dbus_method_invocation_return_error (handle->invocation, G_IO_ERROR, G_IO_ERROR_FAILED, "Unexpected reason: '%s' != '%s'", reason, handle->reason); - return TRUE; - } - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - if (id) - g_variant_builder_add (&opt_builder, "{sv}", "id", g_variant_new_string (id)); - if (name) - g_variant_builder_add (&opt_builder, "{sv}", "name", g_variant_new_string (name)); - if (image) - g_variant_builder_add (&opt_builder, "{sv}", "image", g_variant_new_string (image)); - - if (handle->request->exported) - request_unexport (handle->request); - - g_debug ("send response %d", response); - - xdp_dbus_impl_account_complete_get_user_information (handle->impl, - handle->invocation, - response, - g_variant_builder_end (&opt_builder)); - - handle->timeout = 0; - - account_dialog_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation, - AccountDialogHandle *handle) -{ - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - g_debug ("send response 2"); - xdp_dbus_impl_account_complete_get_user_information (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - account_dialog_handle_free (handle); - - return FALSE; -} - - -static gboolean -handle_get_user_information (XdpDbusImplAccount *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - int delay; - AccountDialogHandle *handle; - const char *reason = NULL; - g_autoptr(Request) request = NULL; - - g_debug ("Handling GetUserInformation"); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "account", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - request = request_new (sender, arg_app_id, arg_handle); - - g_variant_lookup (arg_options, "reason", "&s", &reason); - - handle = g_new0 (AccountDialogHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - handle->reason = g_strdup (reason); - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -void -account_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_account_skeleton_new ()); - - g_signal_connect (helper, "handle-get-user-information", G_CALLBACK (handle_get_user_information), NULL); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/account.h b/tests/backend/account.h deleted file mode 100644 index 120cf9e..0000000 --- a/tests/backend/account.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void account_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/appchooser.c b/tests/backend/appchooser.c deleted file mode 100644 index 0d4ac65..0000000 --- a/tests/backend/appchooser.c +++ /dev/null @@ -1,183 +0,0 @@ -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "appchooser.h" - -typedef struct { - XdpDbusImplAppChooser *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - guint timeout; - char **choices; - GVariant *options; -} AppChooserHandle; - -static void -app_chooser_handle_free (AppChooserHandle *handle) -{ - g_object_unref (handle->impl); - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - if (handle->timeout) - g_source_remove (handle->timeout); - g_strfreev (handle->choices); - g_variant_unref (handle->options); - - g_free (handle); -} - -static gboolean -send_response (gpointer data) -{ - AppChooserHandle *handle = data; - GVariantBuilder opt_builder; - int response; - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - - if (handle->request->exported) - request_unexport (handle->request); - - if (response == 0) - { - if (handle->choices[0]) - { - g_debug ("choice: %s", handle->choices[0]); - g_variant_builder_add (&opt_builder, "{sv}", "choice", g_variant_new_string (handle->choices[0])); - } - } - - g_debug ("send response %d", response); - - xdp_dbus_impl_app_chooser_complete_choose_application (handle->impl, - handle->invocation, - response, - g_variant_builder_end (&opt_builder)); - - handle->timeout = 0; - - app_chooser_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation, - AppChooserHandle *handle) -{ - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - xdp_dbus_impl_app_chooser_complete_choose_application (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - app_chooser_handle_free (handle); - - return FALSE; -} - - -static gboolean -handle_choose_application (XdpDbusImplAppChooser *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - const char * const *arg_choices, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - int delay; - AppChooserHandle *handle; - g_autoptr(Request) request = NULL; - - g_debug ("Handling ChooseApplication"); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "appchooser", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - if (g_key_file_has_key (keyfile, "backend", "expect-no-call", NULL)) - { - g_dbus_method_invocation_return_error (invocation, - G_IO_ERROR, - G_IO_ERROR_FAILED, - "Did not expect ChooseApplication to be called here"); - return TRUE; /* handled */ - } - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new0 (AppChooserHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - handle->choices = g_strdupv ((char **)arg_choices); - handle->options = g_variant_ref (arg_options); - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -void -appchooser_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_app_chooser_skeleton_new ()); - - g_signal_connect (helper, "handle-choose-application", G_CALLBACK (handle_choose_application), NULL); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/appchooser.h b/tests/backend/appchooser.h deleted file mode 100644 index b0ee145..0000000 --- a/tests/backend/appchooser.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void appchooser_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/background.c b/tests/backend/background.c deleted file mode 100644 index 09e10d8..0000000 --- a/tests/backend/background.c +++ /dev/null @@ -1,99 +0,0 @@ -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "background.h" - - -static gboolean -handle_get_app_state (XdpDbusImplBackground *object, - GDBusMethodInvocation *invocation) -{ - GVariantBuilder builder; - - g_debug ("background: handle GetAppState"); - - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - xdp_dbus_impl_background_complete_get_app_state (object, - invocation, - g_variant_builder_end (&builder)); - - return TRUE; -} - -static gboolean -handle_notify_background (XdpDbusImplBackground *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_name) -{ - GVariantBuilder opt_builder; - - g_debug ("background: handle NotifyBackground"); - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - xdp_dbus_impl_background_complete_notify_background (object, - invocation, - 2, - g_variant_builder_end (&opt_builder)); - - return TRUE; -} - -static gboolean -handle_enable_autostart (XdpDbusImplBackground *object, - GDBusMethodInvocation *invocation, - const char *arg_app_id, - gboolean arg_enable, - const char * const *arg_commandline, - guint arg_flags) -{ - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - - g_debug ("background: handle EnableAutostart"); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "background", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - g_assert (arg_enable == g_key_file_get_boolean (keyfile, "background", "autostart", NULL)); - - xdp_dbus_impl_background_complete_enable_autostart (object, invocation, TRUE); - - return TRUE; -} - -void -background_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_background_skeleton_new ()); - - g_signal_connect (helper, "handle-get-app-state", G_CALLBACK (handle_get_app_state), NULL); - g_signal_connect (helper, "handle-notify-background", G_CALLBACK (handle_notify_background), NULL); - g_signal_connect (helper, "handle-enable-autostart", G_CALLBACK (handle_enable_autostart), NULL); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/background.h b/tests/backend/background.h deleted file mode 100644 index 21bbea2..0000000 --- a/tests/backend/background.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void background_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/email.c b/tests/backend/email.c deleted file mode 100644 index 1b62e3a..0000000 --- a/tests/backend/email.c +++ /dev/null @@ -1,212 +0,0 @@ -#include "config.h" -#include - -#include - -#include "xdp-impl-dbus.h" -#include "tests/glib-backports.h" - -#include "email.h" -#include "request.h" - -typedef struct { - XdpDbusImplEmail *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - GVariant *options; - guint timeout; -} EmailHandle; - -static void -email_handle_free (EmailHandle *handle) -{ - g_object_unref (handle->impl); - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - g_variant_unref (handle->options); - if (handle->timeout) - g_source_remove (handle->timeout); - - g_free (handle); -} - -static gboolean -send_response (gpointer data) -{ - EmailHandle *handle = data; - GVariantBuilder opt_builder; - const char *address = NULL; - const char *subject = NULL; - const char *body = NULL; - const char *no_att[1] = { NULL }; - const char **attachments = no_att; - char *s; - int response; - const char * const *addresses; - const char * const *cc; - const char * const *bcc; - char **strv; - - g_variant_lookup (handle->options, "address", "&s", &address); - g_variant_lookup (handle->options, "subject", "&s", &subject); - g_variant_lookup (handle->options, "body", "&s", &body); - g_variant_lookup (handle->options, "attachments", "^a&s", &attachments); - g_variant_lookup (handle->options, "addresses", "^a&s", &addresses); - g_variant_lookup (handle->options, "cc", "^a&s", &cc); - g_variant_lookup (handle->options, "bcc", "^a&s", &bcc); - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - s = g_key_file_get_string (handle->keyfile, "input", "subject", NULL); - g_assert_cmpstr (s, ==, subject); - g_free (s); - s = g_key_file_get_string (handle->keyfile, "input", "body", NULL); - g_assert_cmpstr (s, ==, body); - g_free (s); - - strv = g_key_file_get_string_list (handle->keyfile, "input", "addresses", NULL, NULL); - if (strv) - { - g_assert (addresses != NULL); - g_assert_true (g_strv_equal ((const char * const *)strv, addresses)); - g_strfreev (strv); - } - - strv = g_key_file_get_string_list (handle->keyfile, "input", "cc", NULL, NULL); - if (strv) - { - g_assert (addresses != NULL); - g_assert_true (g_strv_equal ((const char * const *)strv, cc)); - g_strfreev (strv); - } - - strv = g_key_file_get_string_list (handle->keyfile, "input", "bcc", NULL, NULL); - if (strv) - { - g_assert (addresses != NULL); - g_assert_true (g_strv_equal ((const char * const *)strv, bcc)); - g_strfreev (strv); - } - - /* fixme: attachments */ - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - - if (handle->request->exported) - request_unexport (handle->request); - - g_debug ("send response %d", response); - - xdp_dbus_impl_email_complete_compose_email (handle->impl, - handle->invocation, - response, - g_variant_builder_end (&opt_builder)); - - handle->timeout = 0; - - email_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation, - EmailHandle *handle) -{ - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - g_debug ("send response 2"); - xdp_dbus_impl_email_complete_compose_email (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - email_handle_free (handle); - - return FALSE; -} - -static gboolean -handle_compose_email (XdpDbusImplEmail *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - GVariant *arg_options) -{ - g_autoptr(Request) request = NULL; - const char *sender; - g_autoptr(GError) error = NULL; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - EmailHandle *handle; - int delay; - - g_debug ("Handling ComposeEmail"); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "email", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new (EmailHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - handle->options = g_variant_ref (arg_options); - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -void -email_init (GDBusConnection *bus, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_email_skeleton_new ()); - - g_signal_connect (helper, "handle-compose-email", G_CALLBACK (handle_compose_email), NULL); - - if (!g_dbus_interface_skeleton_export (helper, bus, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/email.h b/tests/backend/email.h deleted file mode 100644 index 0dfe7bc..0000000 --- a/tests/backend/email.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void email_init (GDBusConnection *bus, const char *object_path); diff --git a/tests/backend/filechooser.c b/tests/backend/filechooser.c deleted file mode 100644 index 788b9e1..0000000 --- a/tests/backend/filechooser.c +++ /dev/null @@ -1,243 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "account.h" - -typedef struct { - XdpDbusImplFileChooser *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - char *title; - GVariant *options; - guint timeout; -} FileChooserHandle; - -static void -file_chooser_handle_free (FileChooserHandle *handle) -{ - g_object_unref (handle->impl); - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - if (handle->timeout) - g_source_remove (handle->timeout); - g_free (handle->title); - g_variant_unref (handle->options); - g_free (handle); -} - -static gboolean -send_response (gpointer data) -{ - FileChooserHandle *handle = data; - GVariantBuilder opt_builder; - g_autoptr(GVariant) current_filter = NULL; - g_autoptr(GVariant) choices = NULL; - g_autoptr(GVariant) filters = NULL; - g_autofree char *filters_string = NULL; - g_autofree char *current_filter_string = NULL; - g_autofree char *choices_string = NULL; - int response; - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - - g_variant_lookup (handle->options, "filters", "@a(sa(us))", &filters); - filters_string = g_key_file_get_string (handle->keyfile, "backend", "filters", NULL); - if (filters_string) - { - g_autoptr(GVariant) expected = NULL; - g_assert_nonnull (filters); - expected = g_variant_parse (G_VARIANT_TYPE ("a(sa(us))"), filters_string, NULL, NULL, NULL); - g_assert (g_variant_equal (filters, expected)); - } - else - { - g_assert_null (filters); - } - - g_variant_lookup (handle->options, "current_filter", "@(sa(us))", ¤t_filter); - current_filter_string = g_key_file_get_string (handle->keyfile, "backend", "current_filter", NULL); - if (current_filter_string) - { - g_autoptr(GVariant) expected = NULL; - g_assert_nonnull (current_filter); - expected = g_variant_parse (G_VARIANT_TYPE ("(sa(us))"), current_filter_string, NULL, NULL, NULL); - g_assert (g_variant_equal (current_filter, expected)); - } - else - { - g_assert_null (current_filter); - } - - g_variant_lookup (handle->options, "choices", "@a(ssa(ss)s)", &choices); - choices_string = g_key_file_get_string (handle->keyfile, "backend", "choices", NULL); - if (choices_string) - { - g_autoptr(GVariant) expected = NULL; - g_assert_nonnull (choices); - expected = g_variant_parse (G_VARIANT_TYPE ("a(ssa(ss)s)"), choices_string, NULL, NULL, NULL); - g_assert (g_variant_equal (choices, expected)); - } - else - { - g_assert_null (choices); - } - - if (response == 0) - { - g_auto(GStrv) uris = NULL; - g_autofree char *chosen_string = NULL; - - uris = g_key_file_get_string_list (handle->keyfile, "result", "uris", NULL, NULL); - g_variant_builder_add (&opt_builder, "{sv}", "uris", g_variant_new_strv ((const char * const *)uris, -1)); - - chosen_string = g_key_file_get_string (handle->keyfile, "result", "choices", NULL); - if (chosen_string) - { - g_autoptr(GVariant) chosen = NULL; - chosen = g_variant_parse (G_VARIANT_TYPE ("a(ss)"), chosen_string, NULL, NULL, NULL); - g_variant_builder_add (&opt_builder, "{sv}", "choices", chosen); - } - } - - if (handle->request->exported) - request_unexport (handle->request); - - g_debug ("send response %d", response); - - if (strcmp (g_dbus_method_invocation_get_method_name (handle->invocation), "OpenFile") == 0) - xdp_dbus_impl_file_chooser_complete_open_file (handle->impl, - handle->invocation, - response, - g_variant_builder_end (&opt_builder)); - else - xdp_dbus_impl_file_chooser_complete_save_file (handle->impl, - handle->invocation, - response, - g_variant_builder_end (&opt_builder)); - - handle->timeout = 0; - - file_chooser_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation, - FileChooserHandle *handle) -{ - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - g_debug ("send response 2"); - if (strcmp (g_dbus_method_invocation_get_method_name (handle->invocation), "OpenFile") == 0) - xdp_dbus_impl_file_chooser_complete_open_file (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - else - xdp_dbus_impl_file_chooser_complete_save_file (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - file_chooser_handle_free (handle); - - return FALSE; -} - -static gboolean -handle_open_file (XdpDbusImplFileChooser *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - const char *arg_title, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - int delay; - FileChooserHandle *handle; - g_autoptr(Request) request = NULL; - - g_debug ("Handling %s", g_dbus_method_invocation_get_method_name (invocation)); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "filechooser", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new0 (FileChooserHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - handle->title = g_strdup (arg_title); - handle->options = g_variant_ref (arg_options); - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -void -file_chooser_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_file_chooser_skeleton_new ()); - - g_signal_connect (helper, "handle-open-file", G_CALLBACK (handle_open_file), NULL); - g_signal_connect (helper, "handle-save-file", G_CALLBACK (handle_open_file), NULL); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/filechooser.h b/tests/backend/filechooser.h deleted file mode 100644 index da5d430..0000000 --- a/tests/backend/filechooser.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void file_chooser_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/inhibit.c b/tests/backend/inhibit.c deleted file mode 100644 index 868c695..0000000 --- a/tests/backend/inhibit.c +++ /dev/null @@ -1,460 +0,0 @@ -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "session.h" -#include "inhibit.h" - -static GDBusInterfaceSkeleton *inhibit; - -typedef struct { - XdpDbusImplInhibit *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - guint flags; - guint close_id; - int timeout; -} InhibitHandle; - -static void -inhibit_handle_free (InhibitHandle *handle) -{ - g_object_unref (handle->impl); - if (handle->request) - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - - if (handle->timeout) - g_source_remove (handle->timeout); - - g_free (handle); -} - -static gboolean -handle_close (Request *object, - GDBusMethodInvocation *invocation, - gpointer data) -{ - InhibitHandle *handle = g_object_get_data (G_OBJECT (object), "handle"); - - if (object->exported) - request_unexport (object); - - xdp_dbus_impl_request_complete_close (XDP_DBUS_IMPL_REQUEST (object), invocation); - - g_debug ("Handling Close"); - - if (handle) - inhibit_handle_free (handle); - else - g_object_unref (object); - - return TRUE; -} - -static gboolean -send_response (gpointer data) -{ - InhibitHandle *handle = data; - int response; - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - if (response == 0) - { - xdp_dbus_impl_inhibit_complete_inhibit (handle->impl, handle->invocation); - g_object_set_data (G_OBJECT (handle->request), "handle", NULL); - handle->request = NULL; - } - else - g_dbus_method_invocation_return_error (handle->invocation, G_IO_ERROR, G_IO_ERROR_FAILED, "Canceled"); - - handle->timeout = 0; - - inhibit_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_inhibit (XdpDbusImplInhibit *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - guint arg_flags, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - InhibitHandle *handle; - g_autoptr(Request) request = NULL; - int delay; - - g_debug ("Handling Inhibit"); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "inhibit", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - g_assert_cmpuint (arg_flags, ==, g_key_file_get_integer (keyfile, "inhibit", "flags", NULL)); - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new0 (InhibitHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - handle->flags = arg_flags; - - g_object_set_data (G_OBJECT (request), "handle", handle); - handle->close_id = g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), NULL); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -typedef enum { - UNKNOWN = 0, - RUNNING = 1, - QUERY_END = 2, - ENDING = 3 -} SessionState; - -static SessionState session_state = RUNNING; -static gboolean screensaver_active = FALSE; -static guint query_end_timeout; -static GList *active_sessions = NULL; - -static void -emit_state_changed (Session *session) -{ - GVariantBuilder state; - - g_debug ("Emitting StateChanged for session %s", session->id); - - g_variant_builder_init (&state, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&state, "{sv}", "screensaver-active", g_variant_new_boolean (screensaver_active)); - g_variant_builder_add (&state, "{sv}", "session-state", g_variant_new_uint32 (session_state)); - g_signal_emit_by_name (inhibit, "state-changed", session->id, g_variant_builder_end (&state)); -} - -typedef struct -{ - Session parent; - gboolean pending_query_end_response; -} InhibitSession; - -typedef struct _InhibitSessionClass -{ - SessionClass parent_class; -} InhibitSessionClass; - -GType inhibit_session_get_type (void); -G_DEFINE_TYPE (InhibitSession, inhibit_session, session_get_type ()) - -static void -global_set_pending_query_end_response (gboolean pending) -{ - GList *l; - - for (l = active_sessions; l; l = l->next) - { - InhibitSession *session = (InhibitSession *)l->data; - session->pending_query_end_response = pending; - } -} - -static gboolean -global_get_pending_query_end_response (void) -{ - GList *l; - - for (l = active_sessions; l; l = l->next) - { - InhibitSession *session = (InhibitSession *)l->data; - if (session->pending_query_end_response) - return TRUE; - } - - return FALSE; -} - -static void -inhibit_session_close (Session *session) -{ - InhibitSession *inhibit_session = (InhibitSession *)session; - - g_debug ("Closing inhibit session %s", ((Session *)inhibit_session)->id); - - active_sessions = g_list_remove (active_sessions, session); -} - -static void -inhibit_session_finalize (GObject *object) -{ - G_OBJECT_CLASS (inhibit_session_parent_class)->finalize (object); -} - -static void -inhibit_session_init (InhibitSession *inhibit_session) -{ -} - -static void -inhibit_session_class_init (InhibitSessionClass *klass) -{ - GObjectClass *gobject_class; - SessionClass *session_class; - - gobject_class = (GObjectClass *)klass; - gobject_class->finalize = inhibit_session_finalize; - - session_class = (SessionClass *)klass; - session_class->close = inhibit_session_close; -} - -static InhibitSession * -inhibit_session_new (const char *app_id, - const char *session_handle) -{ - InhibitSession *inhibit_session; - - g_debug ("Creating inhibit session %s", session_handle); - - inhibit_session = g_object_new (inhibit_session_get_type (), - "id", session_handle, - NULL); - - active_sessions = g_list_prepend (active_sessions, inhibit_session); - - return inhibit_session; -} - -static void -global_emit_state_changed (void) -{ - GList *l; - - for (l = active_sessions; l; l = l->next) - emit_state_changed ((Session *)l->data); -} - -static void -set_session_state (SessionState state) -{ - const char *names[] = { - "Unknown", "Running", "Query-end", "Ending" - }; - - g_debug ("Session state now: %s", names[state]); - - session_state = state; - - global_emit_state_changed (); -} - -static void global_set_pending_query_end_response (gboolean pending); -static gboolean global_get_pending_query_end_response (void); - -static void -stop_waiting_for_query_end_response (gboolean send_response) -{ - g_debug ("Stop waiting for QueryEndResponse calls"); - - if (query_end_timeout != 0) - { - g_source_remove (query_end_timeout); - query_end_timeout = 0; - } - - global_set_pending_query_end_response (FALSE); -} - -static gboolean -query_end_response (gpointer data) -{ - g_debug ("1 second wait is over"); - - stop_waiting_for_query_end_response (TRUE); - - return G_SOURCE_REMOVE; -} - -static void -wait_for_query_end_response (gpointer data) -{ - if (query_end_timeout != 0) - return; /* we're already waiting */ - - g_debug ("Waiting for up to 1 second for QueryEndResponse calls"); - - query_end_timeout = g_timeout_add (1000, query_end_response, data); - - global_set_pending_query_end_response (TRUE); -} - -static void -maybe_send_quit_response (void) -{ - if (query_end_timeout == 0) - return; - - if (global_get_pending_query_end_response ()) - return; - - g_debug ("No more pending QueryEndResponse calls"); - - stop_waiting_for_query_end_response (TRUE); -} - -static gboolean -change_session_state (gpointer data) -{ - g_autoptr(GKeyFile) keyfile = data; - g_autofree char *change = NULL; - - change = g_key_file_get_string (keyfile, "backend", "change", NULL); - - g_debug ("change session state: %s\n", change); - - if (change && g_str_has_prefix (change, "query-end")) - { - wait_for_query_end_response (NULL); - set_session_state (QUERY_END); - maybe_send_quit_response (); - } - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_create_monitor (XdpDbusImplInhibit *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_session_handle, - const char *arg_app_id, - const char *arg_window) -{ - g_autoptr(GError) error = NULL; - int response; - Session *session; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - int delay; - - g_debug ("Handling CreateMonitor"); - - session_state = RUNNING; - screensaver_active = FALSE; - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "inhibit", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - session = (Session *)inhibit_session_new (arg_app_id, arg_session_handle); - - if (!session_export (session, g_dbus_method_invocation_get_connection (invocation), &error)) - { - g_clear_object (&session); - g_warning ("Failed to create inhibit session: %s", error->message); - response = 2; - goto out; - } - - response = 0; - - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - - g_debug ("delay %d", delay); - - if (delay != 0) - g_timeout_add (delay, change_session_state, g_key_file_ref (keyfile)); - -out: - xdp_dbus_impl_inhibit_complete_create_monitor (object, invocation, response); - if (session) - emit_state_changed (session); - - return TRUE; -} - -static gboolean -handle_query_end_response (XdpDbusImplInhibit *object, - GDBusMethodInvocation *invocation, - const char *arg_session_handle) -{ - InhibitSession *session = (InhibitSession *)lookup_session (arg_session_handle); - - g_debug ("Handle QueryEndResponse for session %s", arg_session_handle); - - if (session) - { - session->pending_query_end_response = FALSE; - maybe_send_quit_response (); - } - - xdp_dbus_impl_inhibit_complete_query_end_response (object, invocation); - - return TRUE; -} - -void -inhibit_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - - inhibit = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_inhibit_skeleton_new ()); - - g_signal_connect (inhibit, "handle-inhibit", G_CALLBACK (handle_inhibit), NULL); - g_signal_connect (inhibit, "handle-create-monitor", G_CALLBACK (handle_create_monitor), NULL); - g_signal_connect (inhibit, "handle-query-end-response", G_CALLBACK (handle_query_end_response), NULL); - - if (!g_dbus_interface_skeleton_export (inhibit, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (inhibit)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (inhibit)->name, object_path); -} diff --git a/tests/backend/inhibit.h b/tests/backend/inhibit.h deleted file mode 100644 index 9a42bd0..0000000 --- a/tests/backend/inhibit.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void inhibit_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/lockdown.c b/tests/backend/lockdown.c deleted file mode 100644 index f5b65ec..0000000 --- a/tests/backend/lockdown.c +++ /dev/null @@ -1,43 +0,0 @@ -#define _GNU_SOURCE 1 - -#include "config.h" -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "lockdown.h" - -static void -property_changed (GObject *object, - GParamSpec *pspec, - gpointer data) -{ - gboolean value; - - g_object_get (object, pspec->name, &value, NULL); - g_debug ("lockdown change: %s: %d", pspec->name, value); -} - -void -lockdown_init (GDBusConnection *bus, - const char *object_path) -{ - GDBusInterfaceSkeleton *helper; - g_autoptr(GError) error = NULL; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_lockdown_skeleton_new ()); - - if (!g_dbus_interface_skeleton_export (helper, bus, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - g_signal_connect (helper, "notify", G_CALLBACK (property_changed), NULL); - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} - diff --git a/tests/backend/lockdown.h b/tests/backend/lockdown.h deleted file mode 100644 index b39262c..0000000 --- a/tests/backend/lockdown.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -void lockdown_init (GDBusConnection *connection, const char *object_path); - -void lockdown_update (void); diff --git a/tests/backend/notification.c b/tests/backend/notification.c deleted file mode 100644 index 0fcb856..0000000 --- a/tests/backend/notification.c +++ /dev/null @@ -1,121 +0,0 @@ -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "notification.h" - -typedef struct { - XdpDbusImplNotification *impl; - char *app_id; - char *id; - char *action; -} ActionData; - -static gboolean -invoke_action (gpointer data) -{ - ActionData *adata = data; - GVariantBuilder builder; - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); - - g_print ("emitting ActionInvoked\n"); - xdp_dbus_impl_notification_emit_action_invoked (adata->impl, - adata->app_id, - adata->id, - adata->action, - g_variant_builder_end (&builder)); - - g_free (adata->app_id); - g_free (adata->id); - g_free (adata->action); - g_free (adata); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_add_notification (XdpDbusImplNotification *object, - GDBusMethodInvocation *invocation, - const gchar *arg_app_id, - const gchar *arg_id, - GVariant *arg_notification) -{ - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autofree char *notification_s = NULL; - g_autoptr(GVariant) notification = NULL; - g_autoptr(GError) error = NULL; - int delay; - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "notification", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - notification_s = g_key_file_get_string (keyfile, "notification", "data", NULL); - notification = g_variant_parse (G_VARIANT_TYPE_VARDICT, notification_s, NULL, NULL, &error); - g_assert_no_error (error); - g_assert_true (g_variant_equal (notification, arg_notification)); - - if (g_key_file_get_boolean (keyfile, "backend", "expect-no-call", NULL)) - g_assert_not_reached (); - - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - if (delay != 0) - { - ActionData *data; - data = g_new (ActionData, 1); - data->impl = object; - data->app_id = g_strdup (arg_app_id); - data->id = g_strdup (arg_id); - data->action = g_key_file_get_string (keyfile, "notification", "action", NULL); - - g_timeout_add (delay, invoke_action, data); - } - - xdp_dbus_impl_notification_complete_add_notification (object, invocation); - - return TRUE; -} - -static gboolean -handle_remove_notification (XdpDbusImplNotification *object, - GDBusMethodInvocation *invocation, - const gchar *arg_app_id, - const gchar *arg_id) -{ - xdp_dbus_impl_notification_complete_remove_notification (object, invocation); - - return TRUE; -} - -void -notification_init (GDBusConnection *bus, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_notification_skeleton_new ()); - - g_signal_connect (helper, "handle-add-notification", G_CALLBACK (handle_add_notification), NULL); - g_signal_connect (helper, "handle-remove-notification", G_CALLBACK (handle_remove_notification), NULL); - - if (!g_dbus_interface_skeleton_export (helper, bus, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} - diff --git a/tests/backend/notification.h b/tests/backend/notification.h deleted file mode 100644 index 629b587..0000000 --- a/tests/backend/notification.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void notification_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/print.c b/tests/backend/print.c deleted file mode 100644 index 3f18e40..0000000 --- a/tests/backend/print.c +++ /dev/null @@ -1,260 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "print.h" - -typedef struct { - XdpDbusImplPrint *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - guint timeout; - char *title; - GVariant *settings; - GVariant *page_setup; - GVariant *options; -} PrintHandle; - -static void -print_handle_free (PrintHandle *handle) -{ - g_object_unref (handle->impl); - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - if (handle->timeout) - g_source_remove (handle->timeout); - g_free (handle->title); - if (handle->settings) - g_variant_unref (handle->settings); - if (handle->page_setup) - g_variant_unref (handle->page_setup); - if (handle->options) - g_variant_unref (handle->options); - - g_free (handle); -} - -static gboolean -send_response (gpointer data) -{ - PrintHandle *handle = data; - GVariantBuilder opt_builder; - int response; - int token; - GVariantBuilder settings; - GVariantBuilder page_setup; - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - - if (handle->request->exported) - request_unexport (handle->request); - - if (strcmp (g_dbus_method_invocation_get_method_name (handle->invocation), "PreparePrint") == 0) - { - token = g_key_file_get_integer (handle->keyfile, "result", "token", NULL); - g_variant_builder_init (&settings, G_VARIANT_TYPE_VARDICT); - g_variant_builder_init (&page_setup, G_VARIANT_TYPE_VARDICT); - - g_variant_builder_add (&opt_builder, "{sv}", "token", g_variant_new_uint32 (token)); - g_variant_builder_add (&opt_builder, "{sv}", "settings", g_variant_builder_end (&settings)); - g_variant_builder_add (&opt_builder, "{sv}", "page-setup", g_variant_builder_end (&page_setup)); - } - - g_debug ("send response %d", response); - - if (strcmp (g_dbus_method_invocation_get_method_name (handle->invocation), "Print") == 0) - xdp_dbus_impl_print_complete_print (handle->impl, - handle->invocation, - NULL, - response, - g_variant_builder_end (&opt_builder)); - else - xdp_dbus_impl_print_complete_prepare_print (handle->impl, - handle->invocation, - response, - g_variant_builder_end (&opt_builder)); - - handle->timeout = 0; - - print_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation, - PrintHandle *handle) -{ - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - g_debug ("send response 2"); - if (strcmp (g_dbus_method_invocation_get_method_name (handle->invocation), "Print") == 0) - xdp_dbus_impl_print_complete_print (handle->impl, - handle->invocation, - NULL, - 2, - g_variant_builder_end (&opt_builder)); - else - xdp_dbus_impl_print_complete_prepare_print (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - print_handle_free (handle); - - return FALSE; -} - - -static gboolean -handle_print (XdpDbusImplPrint *object, - GDBusMethodInvocation *invocation, - GUnixFDList *fd_list, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - const char *arg_title, - GVariant *arg_fd, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - int delay; - PrintHandle *handle; - g_autoptr(Request) request = NULL; - - g_debug ("Handling Print"); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "print", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new0 (PrintHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -static gboolean -handle_prepare_print (XdpDbusImplPrint *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - const char *arg_title, - GVariant *arg_settings, - GVariant *arg_page_setup, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - int delay; - PrintHandle *handle; - g_autoptr(Request) request = NULL; - - g_debug ("Handling Print"); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "print", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new0 (PrintHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} -void -print_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_print_skeleton_new ()); - - g_signal_connect (helper, "handle-print", G_CALLBACK (handle_print), NULL); - g_signal_connect (helper, "handle-prepare-print", G_CALLBACK (handle_prepare_print), NULL); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/print.h b/tests/backend/print.h deleted file mode 100644 index 37c63e1..0000000 --- a/tests/backend/print.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void print_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/request.c b/tests/backend/request.c deleted file mode 100644 index f16d63e..0000000 --- a/tests/backend/request.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright © 2016 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Authors: - * Alexander Larsson - * Matthias Clasen - */ - -#include "request.h" - -#include - -static void request_skeleton_iface_init (XdpDbusImplRequestIface *iface); - -G_DEFINE_TYPE_WITH_CODE (Request, request, XDP_DBUS_IMPL_TYPE_REQUEST_SKELETON, - G_IMPLEMENT_INTERFACE (XDP_DBUS_IMPL_TYPE_REQUEST, - request_skeleton_iface_init)) - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation) -{ - Request *request = (Request *)object; - - if (request->exported) - request_unexport (request); - - xdp_dbus_impl_request_complete_close (XDP_DBUS_IMPL_REQUEST (request), - invocation); - - return TRUE; -} - -static void -request_skeleton_iface_init (XdpDbusImplRequestIface *iface) -{ - iface->handle_close = handle_close; -} - -static void -request_init (Request *request) -{ -} - -static void -request_finalize (GObject *object) -{ - Request *request = (Request *)object; - - g_free (request->sender); - g_free (request->app_id); - g_free (request->id); - - G_OBJECT_CLASS (request_parent_class)->finalize (object); -} - -static void -request_class_init (RequestClass *klass) -{ - GObjectClass *gobject_class; - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = request_finalize; -} - -Request * -request_new (const char *sender, - const char *app_id, - const char *id) -{ - Request *request; - - request = g_object_new (request_get_type (), NULL); - request->sender = g_strdup (sender); - request->app_id = g_strdup (app_id); - request->id = g_strdup (id); - - return request; -} - -void -request_export (Request *request, - GDBusConnection *connection) -{ - g_autoptr(GError) error = NULL; - - if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (request), - connection, - request->id, - &error)) - { - g_warning ("error exporting request: %s\n", error->message); - g_clear_error (&error); - } - - g_object_ref (request); - request->exported = TRUE; -} - -void -request_unexport (Request *request) -{ - request->exported = FALSE; - g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (request)); - g_object_unref (request); -} diff --git a/tests/backend/request.h b/tests/backend/request.h deleted file mode 100644 index 9a150eb..0000000 --- a/tests/backend/request.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright © 2016 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Authors: - * Alexander Larsson - * Matthias Clasen - */ - -#pragma once - -#include "src/xdp-impl-dbus.h" - -typedef struct _Request Request; -typedef struct _RequestClass RequestClass; - -struct _Request -{ - XdpDbusImplRequestSkeleton parent_instance; - - gboolean exported; - char *sender; - char *app_id; - char *id; -}; - -struct _RequestClass -{ - XdpDbusImplRequestSkeletonClass parent_class; -}; - -GType request_get_type (void) G_GNUC_CONST; - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (Request, g_object_unref) - -Request *request_new (const char *sender, - const char *app_id, - const char *id); - -void request_export (Request *request, - GDBusConnection *connection); -void request_unexport (Request *request); diff --git a/tests/backend/screenshot.c b/tests/backend/screenshot.c deleted file mode 100644 index e0f2a61..0000000 --- a/tests/backend/screenshot.c +++ /dev/null @@ -1,200 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "screenshot.h" - -typedef struct { - XdpDbusImplScreenshot *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - guint timeout; - gboolean is_screenshot; -} ScreenshotHandle; - -static void -screenshot_handle_free (ScreenshotHandle *handle) -{ - g_object_unref (handle->impl); - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - if (handle->timeout) - g_source_remove (handle->timeout); - - g_free (handle); -} - -static gboolean -send_response (gpointer data) -{ - ScreenshotHandle *handle = data; - GVariantBuilder opt_builder; - g_autoptr(GVariant) params = NULL; - int response; - g_autofree char *uri = NULL; - double red, green, blue; - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - - if (response == 0) - { - if (handle->is_screenshot) - { - uri = g_key_file_get_string (handle->keyfile, "result", "uri", NULL); - g_variant_builder_add (&opt_builder, "{sv}", "uri", g_variant_new_string (uri)); - } - else - { - red = g_key_file_get_double (handle->keyfile,"result", "red", NULL); - green = g_key_file_get_double (handle->keyfile,"result", "green", NULL); - blue = g_key_file_get_double (handle->keyfile,"result", "blue", NULL); - g_variant_builder_add (&opt_builder, "{sv}", "color", g_variant_new ("(ddd)", red, green, blue)); - } - } - - if (handle->request->exported) - request_unexport (handle->request); - - g_debug ("send response %d", response); - - params = g_variant_ref_sink (g_variant_builder_end (&opt_builder)); - if (handle->is_screenshot) - xdp_dbus_impl_screenshot_complete_screenshot (handle->impl, - handle->invocation, - response, - params); - else - xdp_dbus_impl_screenshot_complete_pick_color (handle->impl, - handle->invocation, - response, - params); - - handle->timeout = 0; - - screenshot_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation, - ScreenshotHandle *handle) -{ - GVariantBuilder opt_builder; - - g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - g_debug ("handling Close"); - if (handle->is_screenshot) - xdp_dbus_impl_screenshot_complete_screenshot (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - else - xdp_dbus_impl_screenshot_complete_pick_color (handle->impl, - handle->invocation, - 2, - g_variant_builder_end (&opt_builder)); - - screenshot_handle_free (handle); - - return FALSE; -} - - -static gboolean -handle_screenshot (XdpDbusImplScreenshot *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - int delay; - ScreenshotHandle *handle; - g_autoptr(Request) request = NULL; - - g_debug ("Handling %s", g_dbus_method_invocation_get_method_name (invocation)); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "screenshot", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new0 (ScreenshotHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - - if (strcmp (g_dbus_method_invocation_get_method_name (invocation), - "Screenshot") == 0) - handle->is_screenshot = TRUE; - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -void -screenshot_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_screenshot_skeleton_new ()); - - xdp_dbus_impl_screenshot_set_version (XDP_DBUS_IMPL_SCREENSHOT (helper), 2); - g_signal_connect (helper, "handle-screenshot", G_CALLBACK (handle_screenshot), NULL); - g_signal_connect (helper, "handle-pick-color", G_CALLBACK (handle_screenshot), NULL); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/screenshot.h b/tests/backend/screenshot.h deleted file mode 100644 index 61ae39b..0000000 --- a/tests/backend/screenshot.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void screenshot_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/session.c b/tests/backend/session.c deleted file mode 100644 index ff0a3af..0000000 --- a/tests/backend/session.c +++ /dev/null @@ -1,195 +0,0 @@ - -/* - * Copyright © 2017 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - */ - -#include "session.h" - -enum -{ - PROP_0, - - PROP_ID, - - PROP_LAST -}; - -static GParamSpec *obj_props[PROP_LAST]; - -static GHashTable *sessions; - -static void session_skeleton_iface_init (XdpDbusImplSessionIface *iface); - -G_DEFINE_TYPE_WITH_CODE (Session, session, XDP_DBUS_IMPL_TYPE_SESSION_SKELETON, - G_IMPLEMENT_INTERFACE (XDP_DBUS_IMPL_TYPE_SESSION, - session_skeleton_iface_init)) - -#define SESSION_GET_CLASS(o) \ - (G_TYPE_INSTANCE_GET_CLASS ((o), session_get_type (), SessionClass)) - -Session * -lookup_session (const char *id) -{ - return g_hash_table_lookup (sessions, id); -} - -gboolean -session_export (Session *session, - GDBusConnection *connection, - GError **error) -{ - if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (session), - connection, - session->id, - error)) - return FALSE; - - g_object_ref (session); - session->exported = TRUE; - - return TRUE; -} - -void -session_unexport (Session *session) -{ - session->exported = FALSE; - g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (session)); - g_object_unref (session); -} - -void -session_close (Session *session) -{ - if (session->exported) - session_unexport (session); - - session->closed = TRUE; - - SESSION_GET_CLASS (session)->close (session); - - g_object_unref (session); -} - -static gboolean -handle_close (XdpDbusImplSession *object, - GDBusMethodInvocation *invocation) -{ - Session *session = (Session *)object; - - if (!session->closed) - session_close (session); - - xdp_dbus_impl_session_complete_close (object, invocation); - - return TRUE; -} - -static void -session_skeleton_iface_init (XdpDbusImplSessionIface *iface) -{ - iface->handle_close = handle_close; -} - -static void -session_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - Session *session = (Session *)object; - - switch (prop_id) - { - case PROP_ID: - session->id = g_strdup (g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -session_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - Session *session = (Session *)object; - - switch (prop_id) - { - case PROP_ID: - g_value_set_string (value, session->id); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -session_finalize (GObject *object) -{ - Session *session = (Session *)object; - - g_hash_table_remove (sessions, session->id); - - g_free (session->id); - - G_OBJECT_CLASS (session_parent_class)->finalize (object); -} - -static void -session_constructed (GObject *object) -{ - Session *session = (Session *)object; - - g_hash_table_insert (sessions, g_strdup (session->id), session); - - G_OBJECT_CLASS (session_parent_class)->constructed (object); -} - -static void -session_init (Session *session) -{ -} - -static void -session_class_init (SessionClass *klass) -{ - GObjectClass *gobject_class; - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->constructed = session_constructed; - gobject_class->finalize = session_finalize; - gobject_class->set_property = session_set_property; - gobject_class->get_property = session_get_property; - - obj_props[PROP_ID] = - g_param_spec_string ("id", "id", "ID", - NULL, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); - - sessions = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, NULL); -} diff --git a/tests/backend/session.h b/tests/backend/session.h deleted file mode 100644 index 874cc96..0000000 --- a/tests/backend/session.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright © 2017 Red Hat, Inc - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - */ - -#pragma once - -#include "src/xdp-impl-dbus.h" - -typedef struct _Session Session; -typedef struct _SessionClass SessionClass; - -struct _Session -{ - XdpDbusImplSessionSkeleton parent; - - gboolean exported; - gboolean closed; - char *id; -}; - -struct _SessionClass -{ - XdpDbusImplSessionSkeletonClass parent_class; - - void (*close) (Session *session); -}; - -GType session_get_type (void); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (Session, g_object_unref) - -Session *lookup_session (const char *id); - -Session *session_new (const char *id); - -void session_close (Session *session); - -gboolean session_export (Session *session, - GDBusConnection *connection, - GError **error); - -void session_unexport (Session *session); diff --git a/tests/backend/settings.c b/tests/backend/settings.c deleted file mode 100644 index bf4d23c..0000000 --- a/tests/backend/settings.c +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "settings.h" - -void -settings_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_settings_skeleton_new ()); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/settings.h b/tests/backend/settings.h deleted file mode 100644 index 4b4c3dc..0000000 --- a/tests/backend/settings.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void settings_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/backend/test-backends.c b/tests/backend/test-backends.c deleted file mode 100644 index bf6261f..0000000 --- a/tests/backend/test-backends.c +++ /dev/null @@ -1,159 +0,0 @@ -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "access.h" -#include "account.h" -#include "appchooser.h" -#include "background.h" -#include "email.h" -#include "filechooser.h" -#include "inhibit.h" -#include "lockdown.h" -#include "notification.h" -#include "print.h" -#include "screenshot.h" -#include "settings.h" -#include "wallpaper.h" - -#include "src/glib-backports.h" - -#define BACKEND_OBJECT_PATH "/org/freedesktop/portal/desktop" - -static GMainLoop *loop; - -static void -on_bus_acquired (GDBusConnection *connection, - const gchar *name, - gpointer user_data) -{ - access_init (connection, BACKEND_OBJECT_PATH); - account_init (connection, BACKEND_OBJECT_PATH); - appchooser_init (connection, BACKEND_OBJECT_PATH); - background_init (connection, BACKEND_OBJECT_PATH); - email_init (connection, BACKEND_OBJECT_PATH); - file_chooser_init (connection, BACKEND_OBJECT_PATH); - inhibit_init (connection, BACKEND_OBJECT_PATH); - lockdown_init (connection, BACKEND_OBJECT_PATH); - notification_init (connection, BACKEND_OBJECT_PATH); - print_init (connection, BACKEND_OBJECT_PATH); - screenshot_init (connection, BACKEND_OBJECT_PATH); - settings_init (connection, BACKEND_OBJECT_PATH); - wallpaper_init (connection, BACKEND_OBJECT_PATH); -} - -static void -on_name_acquired (GDBusConnection *connection, - const gchar *name, - gpointer user_data) -{ - g_debug ("%s acquired", name); -} - -static void -on_name_lost (GDBusConnection *connection, - const gchar *name, - gpointer user_data) -{ - g_debug ("%s lost", name); - g_main_loop_quit (loop); -} - -static gboolean opt_verbose; -static gboolean opt_replace; -static char *opt_backend_name; - -static GOptionEntry entries[] = { - { "backend-name", 0, 0, G_OPTION_ARG_STRING, &opt_backend_name, "The name of the backend on the bus", NULL }, - { "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Print debug information during command processing", NULL }, - { "replace", 'r', 0, G_OPTION_ARG_NONE, &opt_replace, "Replace a running instance", NULL }, - { NULL } -}; - -static void -message_handler (const char *log_domain, - GLogLevelFlags log_level, - const char *message, - gpointer user_data) -{ - if (log_level & G_LOG_LEVEL_DEBUG) - printf ("TST: %s\n", message); - else - printf ("%s: %s\n", g_get_prgname (), message); -} - -static void -printerr_handler (const gchar *string) -{ - int is_tty = isatty (1); - const char *prefix = ""; - const char *suffix = ""; - if (is_tty) - { - prefix = "\x1b[31m\x1b[1m"; /* red, bold */ - suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */ - } - fprintf (stderr, "%serror: %s%s\n", prefix, suffix, string); -} - -int -main (int argc, char *argv[]) -{ - guint owner_id; - g_autoptr(GError) error = NULL; - g_autoptr(GDBusConnection) session_bus = NULL; - g_autoptr(GOptionContext) context = NULL; - - g_log_writer_default_set_use_stderr (TRUE); - - g_setenv ("GIO_USE_VFS", "local", TRUE); - - g_set_prgname (argv[0]); - - context = g_option_context_new ("- portal test backends"); - g_option_context_add_main_entries (context, entries, NULL); - if (!g_option_context_parse (context, &argc, &argv, &error)) - { - g_printerr ("%s: %s", g_get_application_name (), error->message); - g_printerr ("\n"); - return 1; - } - - if (opt_backend_name == NULL) - g_error ("You must specify the name of the backend to own on the bus"); - - g_set_printerr_handler (printerr_handler); - if (opt_verbose) - g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message_handler, NULL); - - loop = g_main_loop_new (NULL, FALSE); - - session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); - if (session_bus == NULL) - { - g_printerr ("No session bus: %s", error->message); - return 2; - } - - g_debug ("Testing backends for '%s'", opt_backend_name); - owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, - opt_backend_name, - G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | (opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0), - on_bus_acquired, - on_name_acquired, - on_name_lost, - NULL, - NULL); - - g_main_loop_run (loop); - - g_bus_unown_name (owner_id); - g_main_loop_unref (loop); - - g_debug ("%s exiting.", g_get_prgname ()); - - return 0; -} diff --git a/tests/backend/wallpaper.c b/tests/backend/wallpaper.c deleted file mode 100644 index dfbf07f..0000000 --- a/tests/backend/wallpaper.c +++ /dev/null @@ -1,170 +0,0 @@ -#include -#include -#include - -#include - -#include "xdp-impl-dbus.h" - -#include "request.h" -#include "wallpaper.h" - -typedef struct { - XdpDbusImplWallpaper *impl; - GDBusMethodInvocation *invocation; - Request *request; - GKeyFile *keyfile; - char *app_id; - guint timeout; - char *uri; - GVariant *options; -} WallpaperHandle; - -static void -wallpaper_handle_free (WallpaperHandle *handle) -{ - g_object_unref (handle->impl); - g_object_unref (handle->request); - g_key_file_unref (handle->keyfile); - g_free (handle->app_id); - if (handle->timeout) - g_source_remove (handle->timeout); - g_free (handle->uri); - g_variant_unref (handle->options); - - g_free (handle); -} - -static gboolean -send_response (gpointer data) -{ - WallpaperHandle *handle = data; - int response; - g_autofree char *s1 = NULL; - const char *s; - gboolean b1, b; - - if (g_key_file_get_boolean (handle->keyfile, "backend", "expect-close", NULL)) - g_assert_not_reached (); - - response = g_key_file_get_integer (handle->keyfile, "backend", "response", NULL); - - if (handle->request->exported) - request_unexport (handle->request); - - s1 = g_key_file_get_string (handle->keyfile, "wallpaper", "target", NULL); - g_variant_lookup (handle->options, "set-on", "&s", &s); - g_assert_cmpstr (s1, ==, s); - - b1 = g_key_file_get_boolean (handle->keyfile, "wallpaper", "preview", NULL); - g_variant_lookup (handle->options, "show-preview", "b", &b); - g_assert_cmpint (b1, ==, b); - - g_debug ("send response %d", response); - - xdp_dbus_impl_wallpaper_complete_set_wallpaper_uri (handle->impl, - handle->invocation, - response); - - handle->timeout = 0; - - wallpaper_handle_free (handle); - - return G_SOURCE_REMOVE; -} - -static gboolean -handle_close (XdpDbusImplRequest *object, - GDBusMethodInvocation *invocation, - WallpaperHandle *handle) -{ - - g_debug ("send response 2"); - xdp_dbus_impl_wallpaper_complete_set_wallpaper_uri (handle->impl, - handle->invocation, - 2); - wallpaper_handle_free (handle); - - return FALSE; -} - - -static gboolean -handle_set_wallpaper_uri (XdpDbusImplWallpaper *object, - GDBusMethodInvocation *invocation, - const char *arg_handle, - const char *arg_app_id, - const char *arg_parent_window, - const char *arg_uri, - GVariant *arg_options) -{ - const char *sender; - const char *dir; - g_autofree char *path = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - int delay; - WallpaperHandle *handle; - g_autoptr(Request) request = NULL; - - g_debug ("Handling SetWallpaperURI"); - - sender = g_dbus_method_invocation_get_sender (invocation); - - dir = g_getenv ("XDG_DATA_HOME"); - path = g_build_filename (dir, "wallpaper", NULL); - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, path, 0, &error); - g_assert_no_error (error); - - request = request_new (sender, arg_app_id, arg_handle); - - handle = g_new0 (WallpaperHandle, 1); - handle->impl = g_object_ref (object); - handle->invocation = invocation; - handle->request = g_object_ref (request); - handle->keyfile = g_key_file_ref (keyfile); - handle->app_id = g_strdup (arg_app_id); - handle->uri = g_strdup (arg_uri); - handle->options = g_variant_ref (arg_options); - - g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); - - request_export (request, g_dbus_method_invocation_get_connection (invocation)); - - if (g_key_file_has_key (keyfile, "backend", "delay", NULL)) - delay = g_key_file_get_integer (keyfile, "backend", "delay", NULL); - else - delay = 200; - - g_debug ("delay %d", delay); - - if (delay == 0) - send_response (handle); - else - handle->timeout = g_timeout_add (delay, send_response, handle); - - return TRUE; -} - -void -wallpaper_init (GDBusConnection *connection, - const char *object_path) -{ - g_autoptr(GError) error = NULL; - GDBusInterfaceSkeleton *helper; - - helper = G_DBUS_INTERFACE_SKELETON (xdp_dbus_impl_wallpaper_skeleton_new ()); - - g_signal_connect (helper, "handle-set-wallpaper-uri", G_CALLBACK (handle_set_wallpaper_uri), NULL); - - if (!g_dbus_interface_skeleton_export (helper, connection, object_path, &error)) - { - g_error ("Failed to export %s skeleton: %s\n", - g_dbus_interface_skeleton_get_info (helper)->name, - error->message); - exit (1); - } - - g_debug ("providing %s at %s", g_dbus_interface_skeleton_get_info (helper)->name, object_path); -} diff --git a/tests/backend/wallpaper.h b/tests/backend/wallpaper.h deleted file mode 100644 index 7f76586..0000000 --- a/tests/backend/wallpaper.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void wallpaper_init (GDBusConnection *connection, const char *object_path); diff --git a/tests/background.c b/tests/background.c deleted file mode 100644 index 8f9223b..0000000 --- a/tests/background.c +++ /dev/null @@ -1,208 +0,0 @@ -#include - -#include "background.h" - -#include -#include "xdp-utils.h" - -extern char outdir[]; - -static int got_info; - -static void -background_cb (GObject *object, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (object); - g_autoptr(GError) error = NULL; - gboolean res; - - res = xdp_portal_request_background_finish (portal, result, &error); - g_assert_true (res); - g_assert_no_error (error); - - got_info = 1; -} - -void -test_background_basic1 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GPtrArray) argv = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autofree char *path = NULL; - g_autoptr(GError) error = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_unref (keyfile); - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "background", "reason", "Testing portals"); - g_key_file_set_boolean (keyfile, "background", "autostart", FALSE); - g_key_file_set_boolean (keyfile, "background", "dbus_activatable", FALSE); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - - path = g_build_filename (outdir, "background", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - argv = g_ptr_array_new (); - g_ptr_array_add (argv, "/bin/true"); - - xdp_portal_request_background (portal, NULL, "Testing portals", argv, 0, NULL, background_cb, NULL); - - while (got_info < 1) - g_main_context_iteration (NULL, TRUE); -} - -void -test_background_basic2 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GPtrArray) argv = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autofree char *path = NULL; - g_autoptr(GError) error = NULL; - XdpBackgroundFlags flags = XDP_BACKGROUND_FLAG_NONE; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_unref (keyfile); - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "background", "reason", "Testing portals"); - g_key_file_set_boolean (keyfile, "background", "autostart", TRUE); - g_key_file_set_boolean (keyfile, "background", "dbus_activatable", TRUE); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - - path = g_build_filename (outdir, "background", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - argv = g_ptr_array_new (); - g_ptr_array_add (argv, "/bin/true"); - - flags = XDP_BACKGROUND_FLAG_AUTOSTART | XDP_BACKGROUND_FLAG_ACTIVATABLE; - xdp_portal_request_background (portal, NULL, "Testing portals", argv, flags, NULL, background_cb, NULL); - - while (got_info < 1) - g_main_context_iteration (NULL, TRUE); -} - -static void -background_fail (GObject *object, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (object); - g_autoptr(GError) error = NULL; - gboolean res; - - res = xdp_portal_request_background_finish (portal, result, &error); - g_assert_false (res); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - - got_info = 1; -} - -void -test_background_commandline (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GPtrArray) argv = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autofree char *path = NULL; - g_autoptr(GError) error = NULL; - XdpBackgroundFlags flags = XDP_BACKGROUND_FLAG_NONE; - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "background", "reason", "Testing portals"); - g_key_file_set_boolean (keyfile, "background", "autostart", TRUE); - g_key_file_set_boolean (keyfile, "background", "dbus_activatable", TRUE); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - - path = g_build_filename (outdir, "background", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - argv = g_ptr_array_new (); - - flags = XDP_BACKGROUND_FLAG_AUTOSTART | XDP_BACKGROUND_FLAG_ACTIVATABLE; - xdp_portal_request_background (portal, NULL, "Testing portals", argv, flags, NULL, background_fail, NULL); - - while (got_info < 1) - g_main_context_iteration (NULL, TRUE); -} - -void -test_background_reason (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GPtrArray) argv = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autofree char *path = NULL; - g_autoptr(GError) error = NULL; - XdpBackgroundFlags flags = XDP_BACKGROUND_FLAG_NONE; - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "background", "reason", "Testing portals"); - g_key_file_set_boolean (keyfile, "background", "autostart", TRUE); - g_key_file_set_boolean (keyfile, "background", "dbus_activatable", TRUE); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - - path = g_build_filename (outdir, "background", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - argv = g_ptr_array_new (); - - flags = XDP_BACKGROUND_FLAG_AUTOSTART | XDP_BACKGROUND_FLAG_ACTIVATABLE; - xdp_portal_request_background (portal, NULL, -"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" -"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" -"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", -argv, flags, NULL, background_fail, NULL); - - while (got_info < 1) - g_main_context_iteration (NULL, TRUE); -} diff --git a/tests/background.h b/tests/background.h deleted file mode 100644 index 9d87810..0000000 --- a/tests/background.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -void test_background_basic1 (void); -void test_background_basic2 (void); -void test_background_commandline (void); -void test_background_reason (void); - diff --git a/tests/camera.c b/tests/camera.c deleted file mode 100644 index c590af9..0000000 --- a/tests/camera.c +++ /dev/null @@ -1,350 +0,0 @@ -#include - -#include "camera.h" - -#include -#include "xdp-utils.h" -#include "xdp-impl-dbus.h" - -#include "utils.h" - -extern char outdir[]; - -static int got_info; - -extern XdpDbusImplPermissionStore *permission_store; -extern XdpDbusImplLockdown *lockdown; - -static void -set_camera_permissions (const char *permission) -{ - const char *permissions[2] = { NULL, NULL }; - g_autoptr(GError) error = NULL; - - permissions[0] = permission; - xdp_dbus_impl_permission_store_call_set_permission_sync (permission_store, - "devices", - TRUE, - "camera", - "", - permissions, - NULL, - &error); - g_assert_no_error (error); -} - -static void -reset_camera_permissions (void) -{ - set_camera_permissions (NULL); -} - -static void -camera_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - GKeyFile *keyfile = data; - int response; - int domain; - int code; - gboolean ret; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - domain = g_key_file_get_integer (keyfile, "result", "error_domain", NULL); - code = g_key_file_get_integer (keyfile, "result", "error_code", NULL); - - ret = xdp_portal_access_camera_finish (portal, result, &error); - - g_debug ("camera cb: %d", g_key_file_get_integer (keyfile, "result", "marker", NULL)); - if (response == 0) - { - g_assert_true (ret); - g_assert_no_error (error); - } - else if (response == 1) - { - g_assert_false (ret); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - } - else if (response == 2) - { - g_assert_false (ret); - g_assert_error (error, domain, code); - } - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_camera_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_camera_permissions (); - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_camera_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_camera_permissions (); - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "result", "marker", 1); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_camera_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_camera_permissions (); - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "result", "marker", 2); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -static gboolean -cancel_call (gpointer data) -{ - GCancellable *cancellable = data; - - g_debug ("cancel call"); - g_cancellable_cancel (cancellable); - - return G_SOURCE_REMOVE; -} - -void -test_camera_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GCancellable) cancellable = NULL; - - reset_camera_permissions (); - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "result", "marker", 3); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - //g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_access_camera (portal, NULL, 0, cancellable, camera_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_camera_lockdown (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_camera_permissions (); - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-camera", - g_variant_new_boolean (TRUE), - &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "result", "marker", 4); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-camera", - g_variant_new_boolean (FALSE), - &error); - g_assert_no_error (error); -} - -/* Test the effect of the user denying the access dialog */ -void -test_camera_no_access1 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_camera_permissions (); - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 2); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* Test the effect of the permissions being stored */ -void -test_camera_no_access2 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - set_camera_permissions ("no"); - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_camera_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_camera_permissions (); - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - xdp_portal_access_camera (portal, NULL, 0, NULL, camera_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} - diff --git a/tests/camera.h b/tests/camera.h deleted file mode 100644 index 86c9034..0000000 --- a/tests/camera.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -void test_camera_basic (void); -void test_camera_delay (void); -void test_camera_cancel (void); -void test_camera_close (void); -void test_camera_lockdown (void); -void test_camera_no_access1 (void); -void test_camera_no_access2 (void); -void test_camera_parallel (void); diff --git a/tests/can-use-fuse.c b/tests/can-use-fuse.c deleted file mode 100644 index d7f7b65..0000000 --- a/tests/can-use-fuse.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2019-2021 Collabora Ltd. - * Copyright 2021 Canonical Ltd. - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include "config.h" - -#include "can-use-fuse.h" - -#include -#include - -#include - -#define FUSE_USE_VERSION 35 -#include - -gchar *cannot_use_fuse = NULL; - -static void pc_init(void *userdata, - struct fuse_conn_info *conn) -{ - struct fuse_session **session_ptr = userdata; - - if (!(conn->capable & FUSE_CAP_SPLICE_READ)) - cannot_use_fuse = g_strdup ("FUSE_CAP_SPLICE_READ"); - else if (!(conn->capable & FUSE_CAP_SPLICE_WRITE)) - cannot_use_fuse = g_strdup ("Missing FUSE_CAP_SPLICE_WRITE"); - else if (!(conn->capable & FUSE_CAP_SPLICE_MOVE)) - cannot_use_fuse = g_strdup ("Missing FUSE_CAP_SPLICE_MOVE"); - else if (!(conn->capable & FUSE_CAP_ATOMIC_O_TRUNC)) - cannot_use_fuse = g_strdup ("Missing FUSE_CAP_ATOMIC_O_TRUNC"); - - fuse_session_exit (*session_ptr); -} - -/* - * If we cannot use FUSE, set cannot_use_fuse and return %FALSE. - */ -gboolean -check_fuse (void) -{ - g_autofree gchar *fusermount = NULL; - g_autofree gchar *path = NULL; - char *argv[] = { "flatpak-fuse-test" }; - const struct fuse_lowlevel_ops pc_oper = { .init = pc_init }; - struct fuse_args args = FUSE_ARGS_INIT (G_N_ELEMENTS (argv), argv); - struct fuse_session *session = NULL; - g_autoptr(GError) error = NULL; - - if (cannot_use_fuse != NULL) - return FALSE; - - if (access ("/dev/fuse", W_OK) != 0) - { - cannot_use_fuse = g_strdup_printf ("access /dev/fuse: %s", - g_strerror (errno)); - return FALSE; - } - - fusermount = g_find_program_in_path ("fusermount3"); - - if (fusermount == NULL) - { - cannot_use_fuse = g_strdup ("fusermount3 not found in PATH"); - return FALSE; - } - - if (!g_file_test (fusermount, G_FILE_TEST_IS_EXECUTABLE)) - { - cannot_use_fuse = g_strdup_printf ("%s not executable", fusermount); - return FALSE; - } - - if (!g_file_test ("/etc/mtab", G_FILE_TEST_EXISTS)) - { - cannot_use_fuse = g_strdup ("fusermount3 won't work without /etc/mtab"); - return FALSE; - } - - path = g_dir_make_tmp ("flatpak-test.XXXXXX", &error); - g_assert_no_error (error); - - session = fuse_session_new (&args, &pc_oper, sizeof (pc_oper), &session); - - if (session == NULL) - { - fuse_opt_free_args (&args); - cannot_use_fuse = g_strdup_printf ("fuse_mount: %s", - g_strerror (errno)); - return FALSE; - } - - if (fuse_session_mount (session, path) != 0) - { - fuse_opt_free_args (&args); - fuse_session_destroy (session); - cannot_use_fuse = g_strdup_printf ("fuse_mount: impossible to mount path " - "'%s': %s", - path, g_strerror (errno)); - return FALSE; - } - - g_assert (cannot_use_fuse == NULL); - fuse_session_loop (session); - - if (cannot_use_fuse != NULL) - { - fuse_opt_free_args (&args); - fuse_session_destroy (session); - return FALSE; - } - - g_test_message ("Successfully set up test FUSE fs on %s", path); - fuse_session_unmount (session); - - if (g_rmdir (path) != 0) - g_error ("rmdir %s: %s", path, g_strerror (errno)); - - fuse_opt_free_args (&args); - fuse_session_destroy (session); - - return TRUE; -} - -gboolean -check_fuse_or_skip_test (void) -{ - if (!check_fuse ()) - { - g_assert (cannot_use_fuse != NULL); - g_test_skip (cannot_use_fuse); - return FALSE; - } - - return TRUE; -} diff --git a/tests/can-use-fuse.h b/tests/can-use-fuse.h deleted file mode 100644 index bfc47e3..0000000 --- a/tests/can-use-fuse.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2019-2021 Collabora Ltd. - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#pragma once - -#include - -extern gchar *cannot_use_fuse; -gboolean check_fuse (void); -gboolean check_fuse_or_skip_test (void); diff --git a/tests/conftest.py b/tests/conftest.py index fac8c78..1163e5a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,77 +2,589 @@ # # This file is formatted with Python Black -from typing import Any, Iterator +from typing import Any, Dict, Iterator, Optional +from types import ModuleType import pytest import dbus import dbusmock +import os +import sys +import tempfile +import subprocess +import time +import signal +from pathlib import Path +from contextlib import chdir -from tests import PortalMock +import gi +gi.require_version("UMockdev", "1.0") +from gi.repository import UMockdev # noqa E402 -class SessionBusMock(dbusmock.DBusTestCase): - def __init__(self): - super().__init__() - self._dbus_con = None - @property - def dbus_con(self) -> "dbus.Bus": - return self._dbus_con +def pytest_configure() -> None: + ensure_environment_set() + ensure_umockdev_loaded() -@pytest.fixture() -def session_bus() -> Iterator[SessionBusMock]: +def pytest_sessionfinish(session, exitstatus): + # Meson and ginsttest-runner expect tests to exit with status 77 if all + # tests were skipped + if exitstatus == pytest.ExitCode.NO_TESTS_COLLECTED: + session.exitstatus = 77 + + +def ensure_environment_set() -> None: + env_vars = [ + "XDG_DESKTOP_PORTAL_PATH", + "XDG_PERMISSION_STORE_PATH", + "XDG_DOCUMENT_PORTAL_PATH", + ] + + if not os.getenv("XDP_VALIDATE_AUTO"): + env_vars += [ + "XDP_VALIDATE_ICON", + "XDP_VALIDATE_SOUND", + ] + else: + os.environ.pop("XDP_VALIDATE_ICON", None) + os.environ.pop("XDP_VALIDATE_SOUND", None) + + for env_var in env_vars: + if not os.getenv(env_var): + raise Exception(f"{env_var} must be set") + + +def ensure_umockdev_loaded() -> None: + umockdev_preload = "libumockdev-preload.so" + preload = os.environ.get("LD_PRELOAD", "") + if umockdev_preload not in preload: + os.environ["LD_PRELOAD"] = f"{umockdev_preload}:{preload}" + os.execv(sys.executable, [sys.executable] + sys.argv) + + +def test_dir() -> Path: + return Path(__file__).resolve().parent + + +@pytest.fixture +def xdg_desktop_portal_path() -> Path: + return Path(os.environ["XDG_DESKTOP_PORTAL_PATH"]) + + +@pytest.fixture +def xdg_permission_store_path() -> Path: + return Path(os.environ["XDG_PERMISSION_STORE_PATH"]) + + +@pytest.fixture +def xdg_document_portal_path() -> Path: + return Path(os.environ["XDG_DOCUMENT_PORTAL_PATH"]) + + +@pytest.fixture(autouse=True) +def create_test_dirs(umockdev: Optional[UMockdev.Testbed]) -> Iterator[None]: + # The umockdev argument is to make sure the testbed + # is created before we create the tmpdir + env_dirs = [ + "HOME", + "TMPDIR", + "XDG_CACHE_HOME", + "XDG_CONFIG_HOME", + "XDG_DATA_HOME", + "XDG_RUNTIME_DIR", + "XDG_DESKTOP_PORTAL_DIR", + ] + + test_root = tempfile.TemporaryDirectory( + prefix="xdp-testroot-", ignore_cleanup_errors=True + ) + + for env_dir in env_dirs: + directory = Path(test_root.name) / env_dir.lower() + directory.mkdir(mode=0o700, parents=True) + os.environ[env_dir] = directory.absolute().as_posix() + + yield + + test_root.cleanup() + + +@pytest.fixture +def xdg_data_home_files() -> Dict[str, bytes]: """ - Fixture to yield a DBusTestCase with a started session bus. + Default fixture which can be used to create files in the temporary + XDG_DATA_HOME directory of the test. """ - bus = SessionBusMock() + return {} + + +@pytest.fixture(autouse=True) +def ensure_xdg_data_home( + create_test_dirs: Any, xdg_data_home_files: Dict[str, bytes] +) -> None: + files = xdg_data_home_files + for name, content in files.items(): + file_path = Path(os.environ["XDG_DATA_HOME"]) / name + file_path.parent.mkdir(parents=True, exist_ok=True) + with open(file_path.absolute().as_posix(), "wb") as f: + f.write(content) + + +@pytest.fixture +def xdg_desktop_portal_dir_files() -> Dict[str, bytes]: + """ + Default fixture which can be used to create files in the temporary + XDG_DESKTOP_PORTAL_DIR directory of the test. + """ + return {} + + +@pytest.fixture +def xdg_desktop_portal_dir_default_files() -> Dict[str, bytes]: + files = {} + + portals = [ + "org.freedesktop.impl.portal.Access", + "org.freedesktop.impl.portal.Account", + "org.freedesktop.impl.portal.AppChooser", + "org.freedesktop.impl.portal.Background", + "org.freedesktop.impl.portal.Clipboard", + "org.freedesktop.impl.portal.DynamicLauncher", + "org.freedesktop.impl.portal.Email", + "org.freedesktop.impl.portal.FileChooser", + "org.freedesktop.impl.portal.GlobalShortcuts", + "org.freedesktop.impl.portal.Inhibit", + "org.freedesktop.impl.portal.InputCapture", + "org.freedesktop.impl.portal.Lockdown", + "org.freedesktop.impl.portal.Notification", + "org.freedesktop.impl.portal.Print", + "org.freedesktop.impl.portal.RemoteDesktop", + "org.freedesktop.impl.portal.Screenshot", + "org.freedesktop.impl.portal.Settings", + "org.freedesktop.impl.portal.Usb", + "org.freedesktop.impl.portal.Wallpaper", + ] + + files["test-portals.conf"] = b""" +[preferred] +default=test; +""" + + files["test.portal"] = """ +[portal] +DBusName=org.freedesktop.impl.portal.Test +Interfaces={} +""".format(";".join(portals)).encode("utf-8") + + return files + + +@pytest.fixture(autouse=True) +def ensure_xdg_desktop_portal_dir( + create_test_dirs: Any, + xdg_desktop_portal_dir_files: Dict[str, bytes], + xdg_desktop_portal_dir_default_files: Dict[str, bytes], +) -> None: + files = xdg_desktop_portal_dir_default_files | xdg_desktop_portal_dir_files + for name, content in files.items(): + file_path = Path(os.environ["XDG_DESKTOP_PORTAL_DIR"]) / name + file_path.parent.mkdir(parents=True, exist_ok=True) + with open(file_path.absolute().as_posix(), "wb") as f: + f.write(content) + + +@pytest.fixture(autouse=True) +def create_test_dbus() -> Iterator[dbusmock.DBusTestCase]: + bus = dbusmock.DBusTestCase() bus.setUp() bus.start_session_bus() - con = bus.get_dbus(system_bus=False) - assert con - bus._dbus_con = con + bus.start_system_bus() + yield bus + bus.tearDown() bus.tearDownClass() +@pytest.fixture(autouse=True) +def create_dbus_monitor(create_test_dbus) -> Iterator[Optional[subprocess.Popen]]: + if not os.getenv("XDP_DBUS_MONITOR"): + yield None + return + + dbus_monitor = subprocess.Popen(["dbus-monitor", "--session"]) + + yield dbus_monitor + + dbus_monitor.terminate() + dbus_monitor.wait() + + +def _get_server_for_module( + busses: dict[dbusmock.BusType, dict[str, dbusmock.SpawnedMock]], + module: ModuleType, + bustype: dbusmock.BusType, +) -> dbusmock.SpawnedMock: + assert bustype in dbusmock.BusType + + try: + return busses[bustype][module.BUS_NAME] + except KeyError: + server = dbusmock.SpawnedMock.spawn_for_name( + module.BUS_NAME, + "/dbusmock", + dbusmock.OBJECT_MANAGER_IFACE, + bustype, + stdout=None, + stderr=None, + ) + + busses[bustype][module.BUS_NAME] = server + return server + + +def _get_main_obj_for_module( + server: dbusmock.SpawnedMock, module: ModuleType, bustype: dbusmock.BusType +) -> dbusmock.DBusMockObject: + try: + server.obj.AddObject( + module.MAIN_OBJ, + "com.example.EmptyInterface", + {}, + [], + dbus_interface=dbusmock.MOCK_IFACE, + ) + except Exception: + pass + + bustype.wait_for_bus_object(module.BUS_NAME, module.MAIN_OBJ) + bus = bustype.get_connection() + return bus.get_object(module.BUS_NAME, module.MAIN_OBJ) + + +def _terminate_mock_p(process: subprocess.Popen) -> None: + process.terminate() + process.wait() + + +def _terminate_servers( + busses: dict[dbusmock.BusType, dict[str, dbusmock.SpawnedMock]], +) -> None: + for server in busses[dbusmock.BusType.SYSTEM].values(): + _terminate_mock_p(server.process) + for server in busses[dbusmock.BusType.SESSION].values(): + _terminate_mock_p(server.process) + + +def _start_template( + busses: dict[dbusmock.BusType, dict[str, dbusmock.SpawnedMock]], + template: str, + bus_name: Optional[str], + params: Dict[str, Any] = {}, +) -> None: + """ + Start the template and potentially start a server for it + """ + module_path_dir = (test_dir()).parent.absolute().as_posix() + template_path = test_dir() / f"templates/{template.lower()}.py" + template = template_path.absolute().as_posix() + + # we cd to the parent dir of the test_dir so that the module search path for + # the templates is the same as opening the modules from here + with chdir(module_path_dir): + module = dbusmock.mockobject.load_module(template) + bustype = ( + dbusmock.BusType.SYSTEM if module.SYSTEM_BUS else dbusmock.BusType.SESSION + ) + + if bus_name: + module.BUS_NAME = bus_name + + server = _get_server_for_module(busses, module, bustype) + main_obj = _get_main_obj_for_module(server, module, bustype) + + main_obj.AddTemplate( + template, + dbus.Dictionary(params, signature="sv"), + dbus_interface=dbusmock.MOCK_IFACE, + ) + + @pytest.fixture -def portal_name() -> str: - raise NotImplementedError("All test files need to define the portal_name fixture") +def template_params() -> dict[str, dict[str, Any]]: + """ + Default fixture for overriding the parameters which should be passed to the + mocking templates. Use required_templates to specify the default parameters + and override it for specific test cases via + + @pytest.mark.parametrize("template_params", ({"Template": {"foo": "bar"}},)) + + """ + return {} @pytest.fixture -def portal_has_impl() -> bool: +def required_templates() -> dict[str, dict[str, Any]]: """ - Default fixture for signaling that a portal has an impl.portal as well. + Default fixture for enumerating the mocking templates the test case requires + to be started. This is a map from a template spec to the parameters which + should be passed to the template. The template spec is the name of a + template in the templates directory and an optional dbus bus name to start + the template at, separated by a colon. + + This starts the `settings` and `email` templates on the bus names specified + with BUS_NAME in the templates and passes parameters to settings. + + { + "settings": { + "some_param": true, + }, + "email": {}, + } + + This starts two instances of the settings template with their own parameters + once on the bus name specified in BUS_NAME in the template, and once on the + bus name `org.freedesktop.impl.portal.OtherImpl`. + + { + "settings": { + "some_param": true, + }, + "settings:org.freedesktop.impl.portal.OtherImpl": { + "some_param": false, + }, + } - For tests of portals that do not have an impl, override this fixture to - return False in the respective test_foo.py. """ - return True + return {} @pytest.fixture -def params() -> dict[str, Any]: +def templates( + required_templates: dict[str, dict[str, Any]], + template_params: dict[str, dict[str, Any]], +) -> Iterator[None]: + """ + Fixture which starts the required templates with their parameters. Usually + the `portals` fixture is what you're looking for because it also starts + the portal frontend and the permission store. """ - Default fixture providing empty parameters that get passed to the impl.portal. - To use this in test cases, pass the parameters via + busses: dict[dbusmock.BusType, dict[str, dbusmock.SpawnedMock]] = { + dbusmock.BusType.SYSTEM: {}, + dbusmock.BusType.SESSION: {}, + } + for template_data, params in required_templates.items(): + template, bus_name = (template_data.split(":") + [None])[:2] + assert template + params = template_params.get(template, params) + _start_template(busses, template, bus_name, params) + yield + _terminate_servers(busses) - @pytest.mark.parametrize("params", ({"foo": "bar"}, )) - Note that this must be a tuple as pytest will iterate over the value. +@pytest.fixture +def xdp_overwrite_env() -> dict[str, str]: + """ + Default fixture which can be used to override the environment that gets + passed to xdg-desktop-portal, xdg-document-portal and xdg-permission-store. """ return {} @pytest.fixture -def portal_mock(session_bus, portal_name, params, portal_has_impl) -> PortalMock: +def app_id() -> str: + """ + Default fixture which can be used to override the app id that the portal + frontend will discover for incoming connections. + """ + return "org.example.Test" + + +@pytest.fixture +def xdp_env( + xdp_overwrite_env: dict[str, str], + app_id: str, + usb_queries: Optional[str], + umockdev: Optional[UMockdev.Testbed], +) -> dict[str, str]: + env = os.environ.copy() + env["G_DEBUG"] = "fatal-criticals" + env["XDG_CURRENT_DESKTOP"] = "test" + + if app_id: + env["XDG_DESKTOP_PORTAL_TEST_APP_ID"] = app_id + + if usb_queries: + env["XDG_DESKTOP_PORTAL_TEST_USB_QUERIES"] = usb_queries + + if umockdev: + env["UMOCKDEV_DIR"] = umockdev.get_root_dir() + + asan_suppression = test_dir() / "asan.suppression" + if not asan_suppression.exists(): + raise FileNotFoundError(f"{asan_suppression} does not exist") + env["LSAN_OPTIONS"] = f"suppressions={asan_suppression}" + + for key, val in xdp_overwrite_env.items(): + env[key] = val + + return env + + +def _maybe_add_asan_preload(executable: Path, env: dict[str, str]) -> None: + # ASAN really wants to be the first library to get loaded but we also + # LD_PRELOAD umockdev and LD_PRELOAD gets loaded before any "normally" + # linked libraries. This uses ldd to find the version of libasan.so that + # should be loaded and puts it in front of LD_PRELOAD. + # This way, LD_PRELOAD and ASAN can be used at the same time. + ldd = subprocess.check_output(["ldd", executable]).decode("utf-8") + libs = [line.split()[0] for line in ldd.splitlines()] + try: + libasan = next(filter(lambda lib: lib.startswith("libasan"), libs)) + except StopIteration: + return + + preload = env.get("LD_PRELOAD", "") + env["LD_PRELOAD"] = f"{libasan}:{preload}" + + +@pytest.fixture +def xdg_desktop_portal( + dbus_con: dbus.Bus, xdg_desktop_portal_path: Path, xdp_env: dict[str, str] +) -> Iterator[subprocess.Popen]: + """ + Fixture which starts and eventually stops xdg-desktop-portal + """ + if not xdg_desktop_portal_path.exists(): + raise FileNotFoundError(f"{xdg_desktop_portal_path} does not exist") + + env = xdp_env.copy() + _maybe_add_asan_preload(xdg_desktop_portal_path, env) + + xdg_desktop_portal = subprocess.Popen([xdg_desktop_portal_path], env=env) + + while not dbus_con.name_has_owner("org.freedesktop.portal.Desktop"): + returncode = xdg_desktop_portal.poll() + if returncode is not None: + raise subprocess.SubprocessError( + f"xdg-desktop-portal exited with {returncode}" + ) + time.sleep(0.1) + + yield xdg_desktop_portal + + xdg_desktop_portal.send_signal(signal.SIGHUP) + returncode = xdg_desktop_portal.wait() + assert returncode == 0 + + +@pytest.fixture +def xdg_permission_store( + dbus_con: dbus.Bus, xdg_permission_store_path: Path, xdp_env: dict[str, str] +) -> Iterator[subprocess.Popen]: + """ + Fixture which starts and eventually stops xdg-permission-store + """ + if not xdg_permission_store_path.exists(): + raise FileNotFoundError(f"{xdg_permission_store_path} does not exist") + + env = xdp_env.copy() + _maybe_add_asan_preload(xdg_permission_store_path, env) + + permission_store = subprocess.Popen([xdg_permission_store_path], env=env) + + while not dbus_con.name_has_owner("org.freedesktop.impl.portal.PermissionStore"): + returncode = permission_store.poll() + if returncode is not None: + raise subprocess.SubprocessError( + f"xdg-permission-store exited with {returncode}" + ) + time.sleep(0.1) + + yield permission_store + + permission_store.send_signal(signal.SIGHUP) + permission_store.wait() + # The permission store does not shut down cleanly currently + # returncode = permission_store.wait() + # assert returncode == 0 + + +@pytest.fixture +def xdg_document_portal( + dbus_con: dbus.Bus, xdg_document_portal_path: Path, xdp_env: dict[str, str] +) -> Iterator[subprocess.Popen]: + """ + Fixture which starts and eventually stops xdg-document-portal + """ + if not xdg_document_portal_path.exists(): + raise FileNotFoundError(f"{xdg_document_portal_path} does not exist") + + # FUSE and LD_PRELOAD don't like each other. Not sure what exactly is going + # wrong but it usually just results in a weird hang that needs SIGKILL + env = xdp_env.copy() + env.pop("LD_PRELOAD", None) + + document_portal = subprocess.Popen([xdg_document_portal_path], env=env) + + while not dbus_con.name_has_owner("org.freedesktop.portal.Documents"): + returncode = document_portal.poll() + if returncode is not None: + raise subprocess.SubprocessError( + f"xdg-document-portal exited with {returncode}" + ) + time.sleep(0.1) + + yield document_portal + + document_portal.send_signal(signal.SIGHUP) + returncode = document_portal.wait() + assert returncode == 0 + + +@pytest.fixture +def portals(templates: Any, xdg_desktop_portal: Any, xdg_permission_store: Any) -> None: + """ + Fixture which starts the required templates, xdg-desktop-portal, + xdg-document-portal and xdg-permission-store. Most tests require this. + """ + return None + + +@pytest.fixture +def usb_queries() -> Optional[str]: + """ + Default fixture providing the usb queries the connecting process can + enumerate + """ + return None + + +@pytest.fixture +def umockdev() -> Optional[UMockdev.Testbed]: + """ + Default fixture providing a umockdev testbed + """ + return None + + +@pytest.fixture +def dbus_con(create_test_dbus: dbusmock.DBusTestCase) -> dbus.Bus: + """ + Default fixture which provides the python-dbus session bus of the test. + """ + con = create_test_dbus.get_dbus(system_bus=False) + assert con + return con + + +@pytest.fixture +def dbus_con_sys(create_test_dbus: dbusmock.DBusTestCase) -> dbus.Bus: """ - Fixture yielding a PortalMock object with the impl started, if applicable. + Default fixture which provides the python-dbus system bus of the test. """ - pmock = PortalMock(session_bus, portal_name) - if portal_has_impl: - pmock.start_impl_portal(params) - pmock.start_xdp() - return pmock + con_sys = create_test_dbus.get_dbus(system_bus=True) + assert con_sys + return con_sys diff --git a/tests/dbs/meson.build b/tests/dbs/meson.build index b9896c4..2e9f4b7 100644 --- a/tests/dbs/meson.build +++ b/tests/dbs/meson.build @@ -1,5 +1,4 @@ -# FIXME: should be installed if installed_tests -dbs = configure_file(input: 'no_tables', +configure_file(input: 'no_tables', output: '@PLAINNAME@', copy: true ) diff --git a/tests/email.c b/tests/email.c deleted file mode 100644 index b3c9fc6..0000000 --- a/tests/email.c +++ /dev/null @@ -1,462 +0,0 @@ -#include - -#include "email.h" - -#include -#include "xdp-utils.h" - -extern char outdir[]; - -static int got_info; - -static void -email_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - gboolean ret; - GKeyFile *keyfile = data; - int response; - int domain; - int code; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - domain = g_key_file_get_integer (keyfile, "result", "error_domain", NULL); - code = g_key_file_get_integer (keyfile, "result", "error_code", NULL); - - ret = xdp_portal_compose_email_finish (portal, result, &error); - g_assert (ret == (response == 0)); - if (response == 0) - g_assert_no_error (error); - else if (response == 1) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - else if (response == 2) - g_assert_error (error, domain, code); - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -/* some basic tests using libportal, and test that communication - * with the backend via keyfile works - */ -void -test_email_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char *addresses[2] = { "mclasen@redhat.com", NULL }; - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "input", "address", "mclasen@redhat.com"); - g_key_file_set_string (keyfile, "input", "subject", "Re: portal tests"); - g_key_file_set_string (keyfile, "input", "body", "You have to see this..."); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "email", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - "Re: portal tests", - "You have to see this...", - NULL, - 0, - NULL, - email_cb, - keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* test that an invalid address triggers an error - */ -void -test_email_address (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char *addresses[2];; - - keyfile = g_key_file_new (); - - addresses[0] = "gibberish! not an email address\n%Q"; - addresses[1] = NULL; - - g_key_file_set_string_list (keyfile, "input", "addresses", addresses, 1); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - - path = g_build_filename (outdir, "email", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - NULL, - NULL, - NULL, - 0, - NULL, - email_cb, - keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* test that punycode-encoded email addresses pass validation - */ -void -test_email_punycode_address (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char *addresses[2] = { "xn--franais-xxa@exemple.fr", NULL }; - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "input", "address", "xn--franais-xxa@exemple.fr"); - g_key_file_set_string (keyfile, "input", "subject", "Re: portal tests"); - g_key_file_set_string (keyfile, "input", "body", "To ASCII and beyond"); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "email", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - "Re: portal tests", - "To ASCII and beyond", - NULL, - 0, - NULL, - email_cb, - keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* test that an invalid subject triggers an error - */ -void -test_email_subject (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char *subject; - - keyfile = g_key_file_new (); - - subject = "not\na\nvalid\nsubject line"; - - g_key_file_set_string (keyfile, "input", "subject", subject); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - - path = g_build_filename (outdir, "email", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - NULL, NULL, NULL, - subject, - NULL, - NULL, - 0, - NULL, - email_cb, - keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - subject = "This subject line is too long, much too long. It is more than twohundred characters long, which is much, much too long for a reasonable subject line. Be concise! This is not twitter where you can use hundreds of characters, including Emoji like 😂️ or 😩️"; - g_assert_cmpint (g_utf8_strlen (subject, -1), >, 200); - - g_key_file_set_string (keyfile, "input", "subject", subject); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - NULL, NULL, NULL, - subject, - NULL, - NULL, - 0, - NULL, - email_cb, - keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* test that everything works as expected when the - * backend takes some time to send its response, as - * is to be expected from a real backend that presents - * dialogs to the user. - */ -void -test_email_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char *addresses[2]; - const char *subject; - - addresses[0] = "mclasen@redhat.com"; - addresses[1] = NULL; - subject = "delay test"; - - keyfile = g_key_file_new (); - g_key_file_set_string_list (keyfile, "input", "addresses", addresses, 1); - g_key_file_set_string (keyfile, "input", "subject", subject); - - g_key_file_set_integer (keyfile, "backend", "delay", 400); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "email", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - subject, - NULL, - NULL, - 0, - NULL, - email_cb, - keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* Test that user cancellation works as expected. - * We simulate that the user cancels a hypothetical dialog, - * by telling the backend to return 1 as response code. - * And we check that we get the expected G_IO_ERROR_CANCELLED. - */ -void -test_email_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char *addresses[2]; - const char *subject; - - addresses[0] = "mclasen@redhat.com"; - addresses[1] = NULL; - subject = "delay test"; - - keyfile = g_key_file_new (); - g_key_file_set_string_list (keyfile, "input", "addresses", addresses, 1); - g_key_file_set_string (keyfile, "input", "subject", subject); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "email", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - subject, - NULL, - NULL, - 0, - NULL, - email_cb, - keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -static gboolean -cancel_call (gpointer data) -{ - GCancellable *cancellable = data; - - g_debug ("cancel call"); - g_cancellable_cancel (cancellable); - - return G_SOURCE_REMOVE; -} - -/* Test that app-side cancellation works as expected. - * We cancel the cancellable while while the hypothetical - * dialog is up, and tell the backend that it should - * expect a Close call. We rely on the backend to - * verify that that call actually happened. - */ -void -test_email_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GCancellable) cancellable = NULL; - const char *addresses[2]; - const char *subject; - - addresses[0] = "mclasen@redhat.com"; - addresses[1] = NULL; - subject = "delay test"; - - keyfile = g_key_file_new (); - g_key_file_set_string_list (keyfile, "input", "addresses", addresses, 1); - g_key_file_set_string (keyfile, "input", "subject", subject); - - g_key_file_set_integer (keyfile, "backend", "delay", 400); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "email", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - subject, - NULL, - NULL, - 0, - cancellable, - email_cb, - keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_email_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char *addresses[2]; - - addresses[0] = "mclasen@redhat.com"; - addresses[1] = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_string_list (keyfile, "input", "addresses", addresses, 1); - g_key_file_set_string (keyfile, "input", "subject", "Re: portal tests"); - g_key_file_set_string (keyfile, "input", "body", "You have to see this..."); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "email", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - "Re: portal tests", - "You have to see this...", - NULL, - 0, - NULL, - email_cb, - keyfile); - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - "Re: portal tests", - "You have to see this...", - NULL, - 0, - NULL, - email_cb, - keyfile); - xdp_portal_compose_email (portal, NULL, - addresses, NULL, NULL, - "Re: portal tests", - "You have to see this...", - NULL, - 0, - NULL, - email_cb, - keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} - diff --git a/tests/email.h b/tests/email.h deleted file mode 100644 index f813b15..0000000 --- a/tests/email.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -void test_email_basic (void); -void test_email_address (void); -void test_email_punycode_address (void); -void test_email_subject (void); -void test_email_delay (void); -void test_email_cancel (void); -void test_email_close (void); -void test_email_parallel (void); diff --git a/tests/filechooser.c b/tests/filechooser.c deleted file mode 100644 index 6565d8d..0000000 --- a/tests/filechooser.c +++ /dev/null @@ -1,945 +0,0 @@ -#include - -#include "account.h" - -#include "glib-backports.h" - -#include -#include "xdp-utils.h" -#include "xdp-impl-dbus.h" - -#include "utils.h" - -extern XdpDbusImplLockdown *lockdown; - -extern char outdir[]; - -static int got_info; - -static void -open_file_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - g_autoptr(GVariant) ret = NULL; - GKeyFile *keyfile = data; - int response; - int domain; - int code; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - domain = g_key_file_get_integer (keyfile, "result", "error_domain", NULL); - code = g_key_file_get_integer (keyfile, "result", "error_code", NULL); - - ret = xdp_portal_open_file_finish (portal, result, &error); - if (response == 0) - { - g_autofree const char * const *uris = NULL; - g_auto(GStrv) expected_uris = NULL; - g_autofree char *expected_choices = NULL; - g_autoptr(GVariant) choices = NULL; - - g_assert_no_error (error); - g_variant_lookup (ret, "uris", "^a&s", &uris); - expected_uris = g_key_file_get_string_list (keyfile, "result", "uris", NULL, NULL); - - g_assert (g_strv_equal (uris, (const char * const *)expected_uris)); - - expected_choices = g_key_file_get_string (keyfile, "result", "choices", NULL); - g_variant_lookup (ret, "choices", "@a(ss)", &choices); - if (expected_choices) - { - g_autoptr(GVariant) c = NULL; - g_assert_nonnull (choices); - c = g_variant_parse (G_VARIANT_TYPE ("a(ss)"), expected_choices, NULL, NULL, NULL); - g_assert_true (g_variant_equal (choices, c)); - } - else - { - g_assert_null (choices); - } - } - else if (response == 1) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - else if (response == 2) - g_assert_error (error, domain, code); - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_open_file_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -static gboolean -cancel_call (gpointer data) -{ - GCancellable *cancellable = data; - - g_debug ("cancel call"); - g_cancellable_cancel (cancellable); - - return G_SOURCE_REMOVE; -} - -void -test_open_file_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GCancellable) cancellable = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 1); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, NULL, 0, cancellable, open_file_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_multiple (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - "file:///test/file2", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_filters1 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - NULL - }; - g_autoptr(GVariant) filters = NULL; - const char *filter_string = - "[('Images', [(0, '*ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]"; - - filters = g_variant_parse (G_VARIANT_TYPE ("a(sa(us))"), filter_string, NULL, NULL, &error); - - keyfile = g_key_file_new (); - - g_assert_no_error (error); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "filters", filter_string); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", filters, NULL, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_filters2 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - NULL - }; - g_autoptr(GVariant) filters = NULL; - const char *filter_string = - "[('Images', [(0, '*ico'), (1, 'image/png')]), ('Text', [(4, '*.txt')])]"; /* invalid type */ - - filters = g_variant_parse (G_VARIANT_TYPE ("a(sa(us))"), filter_string, NULL, NULL, &error); - - keyfile = g_key_file_new (); - - g_assert_no_error (error); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "filters", filter_string); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", filters, NULL, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_current_filter1 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - NULL - }; - g_autoptr(GVariant) filters = NULL; - g_autoptr(GVariant) current_filter = NULL; - const char *filter_string = "[('Images', [(0, '*ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]"; - const char *current_filter_string = "('Text', [(0, '*.txt')])"; - - filters = g_variant_parse (G_VARIANT_TYPE ("a(sa(us))"), filter_string, NULL, NULL, &error); - g_assert_no_error (error); - - current_filter = g_variant_parse (G_VARIANT_TYPE ("(sa(us))"), current_filter_string, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "filters", filter_string); - g_key_file_set_string (keyfile, "backend", "current_filter", current_filter_string); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", filters, current_filter, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_current_filter2 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - NULL - }; - g_autoptr(GVariant) current_filter = NULL; - const char *current_filter_string = "('Text', [(0, '*.txt')])"; - - current_filter = g_variant_parse (G_VARIANT_TYPE ("(sa(us))"), current_filter_string, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "current_filter", current_filter_string); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, current_filter, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_current_filter3 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - NULL - }; - g_autoptr(GVariant) current_filter = NULL; - const char *current_filter_string = "('Text', [(6, '*.txt')])"; /* invalid type */ - - current_filter = g_variant_parse (G_VARIANT_TYPE ("(sa(us))"), current_filter_string, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "current_filter", current_filter_string); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, current_filter, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_current_filter4 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - NULL - }; - g_autoptr(GVariant) filters = NULL; - g_autoptr(GVariant) current_filter = NULL; - const char *filter_string = "[('Images', [(0, '*ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]"; - const char *current_filter_string = "('Something else', [(0, '*.sth.else')])"; /* not in the list */ - - filters = g_variant_parse (G_VARIANT_TYPE ("a(sa(us))"), filter_string, NULL, NULL, &error); - g_assert_no_error (error); - - current_filter = g_variant_parse (G_VARIANT_TYPE ("(sa(us))"), current_filter_string, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "filters", filter_string); - g_key_file_set_string (keyfile, "backend", "current_filter", current_filter_string); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", filters, current_filter, NULL, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_choices1 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - NULL - }; - g_autoptr(GVariant) choices = NULL; - const char *choices_string = "[('encoding', 'Encoding', [('utf8', 'Unicode'), ('latin15', 'Western')], 'latin15'), ('reencode', 'Reencode', [], 'false'), ('third', 'Third', [('a', 'A'), ('b', 'B')], '')]"; - const char *chosen_string = "[('encoding', 'utf8'), ('reencode', 'true'), ('third', 'a')]"; - - choices = g_variant_parse (G_VARIANT_TYPE ("a(ssa(ss)s)"), choices_string, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "choices", choices_string); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string (keyfile, "result", "choices", chosen_string); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, choices, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_choices2 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GVariant) choices = NULL; - const char *choices_string = "[('encoding', 'Encoding', [('utf8', ''), ('latin15', 'Western')], 'latin15'), ('reencode', 'Reencode', [], 'false')]"; /* invalid: empty label */ - - choices = g_variant_parse (G_VARIANT_TYPE ("a(ssa(ss)s)"), choices_string, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "choices", choices_string); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, choices, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_choices3 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GVariant) choices = NULL; - const char *choices_string = "[('', 'Encoding', [('utf8', 'Unicode'), ('latin15', 'Western')], 'latin15'), ('reencode', 'Reencode', [], 'false')]"; /* invalid: empty id */ - - choices = g_variant_parse (G_VARIANT_TYPE ("a(ssa(ss)s)"), choices_string, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "choices", choices_string); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, choices, 0, NULL, open_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_file_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, NULL, 0, NULL, open_file_cb, keyfile); - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, NULL, 0, NULL, open_file_cb, keyfile); - xdp_portal_open_file (portal, NULL, "test", NULL, NULL, NULL, 0, NULL, open_file_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} - -/* tests of SaveFile below */ - -static void -save_file_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - g_autoptr(GVariant) ret = NULL; - GKeyFile *keyfile = data; - int response; - int domain; - int code; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - domain = g_key_file_get_integer (keyfile, "result", "error_domain", NULL); - code = g_key_file_get_integer (keyfile, "result", "error_code", NULL); - - ret = xdp_portal_save_file_finish (portal, result, &error); - if (response == 0) - { - g_autofree const char * const *uris = NULL; - g_auto(GStrv) expected = NULL; - - g_assert_no_error (error); - g_variant_lookup (ret, "uris", "^a&s", &uris); - expected = g_key_file_get_string_list (keyfile, "result", "uris", NULL, NULL); - - g_assert (g_strv_equal (uris, (const char * const *)expected)); - } - else if (response == 1) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - else if (response == 2) - g_assert_error (error, domain, code); - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_save_file_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, NULL, NULL, NULL, 0, NULL, save_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_save_file_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, NULL, NULL, NULL, 0, NULL, save_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_save_file_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, NULL, NULL, NULL, 0, NULL, save_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_save_file_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GCancellable) cancellable = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 1); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, NULL, NULL, NULL, 0, cancellable, save_file_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_save_file_filters (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file1", - NULL - }; - g_autoptr(GVariant) filters = NULL; - const char *filter_string = - "[('Images', [(0, '*ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]"; - - filters = g_variant_parse (G_VARIANT_TYPE ("a(sa(us))"), filter_string, NULL, NULL, &error); - - keyfile = g_key_file_new (); - - g_assert_no_error (error); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "backend", "filters", filter_string); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, filters, NULL, NULL, 0, NULL, save_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_save_file_lockdown (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-save-to-disk", - g_variant_new_boolean (TRUE), - &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, NULL, NULL, NULL, 0, NULL, save_file_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-save-to-disk", - g_variant_new_boolean (FALSE), - &error); - g_assert_no_error (error); -} - -void -test_save_file_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - const char * uris[] = { - "file:///test/file", - NULL - }; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string_list (keyfile, "result", "uris", uris, g_strv_length ((char **)uris)); - - path = g_build_filename (outdir, "filechooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, NULL, NULL, NULL, 0, NULL, save_file_cb, keyfile); - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, NULL, NULL, NULL, 0, NULL, save_file_cb, keyfile); - xdp_portal_save_file (portal, NULL, "test", "test_file.txt", NULL, NULL, NULL, NULL, NULL, 0, NULL, save_file_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} diff --git a/tests/filechooser.h b/tests/filechooser.h deleted file mode 100644 index 523e4a9..0000000 --- a/tests/filechooser.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -void test_open_file_basic (void); -void test_open_file_delay (void); -void test_open_file_cancel (void); -void test_open_file_close (void); -void test_open_file_multiple (void); -void test_open_file_filters1 (void); -void test_open_file_filters2 (void); -void test_open_file_current_filter1 (void); -void test_open_file_current_filter2 (void); -void test_open_file_current_filter3 (void); -void test_open_file_current_filter4 (void); -void test_open_file_choices1 (void); -void test_open_file_choices2 (void); -void test_open_file_choices3 (void); -void test_open_file_parallel (void); - -void test_save_file_basic (void); -void test_save_file_delay (void); -void test_save_file_cancel (void); -void test_save_file_close (void); -void test_save_file_filters (void); -void test_save_file_lockdown (void); -void test_save_file_parallel (void); diff --git a/tests/glib-backports.c b/tests/glib-backports.c deleted file mode 100644 index 256dc37..0000000 --- a/tests/glib-backports.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "glib-backports.h" - -#if !GLIB_CHECK_VERSION(2,60,0) - -gboolean -g_strv_equal (const gchar * const *strv1, - const gchar * const *strv2) -{ - g_return_val_if_fail (strv1 != NULL, FALSE); - g_return_val_if_fail (strv2 != NULL, FALSE); - - if (strv1 == strv2) - return TRUE; - - for (; *strv1 != NULL && *strv2 != NULL; strv1++, strv2++) - { - if (!g_str_equal (*strv1, *strv2)) - return FALSE; - } - - return (*strv1 == NULL && *strv2 == NULL); -} - -#endif diff --git a/tests/glib-backports.h b/tests/glib-backports.h deleted file mode 100644 index 62d317b..0000000 --- a/tests/glib-backports.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include - -#if !GLIB_CHECK_VERSION(2,60,0) -gboolean g_strv_equal (const gchar * const *strv1, - const gchar * const *strv2); -#endif diff --git a/tests/inhibit.c b/tests/inhibit.c deleted file mode 100644 index 473c964..0000000 --- a/tests/inhibit.c +++ /dev/null @@ -1,404 +0,0 @@ -#include - -#include "inhibit.h" - -#include -#include "xdp-impl-dbus.h" - -extern char outdir[]; - -extern XdpDbusImplPermissionStore *permission_store; - -static void -set_inhibit_permissions (const char **permissions) -{ - g_autoptr(GError) error = NULL; - - xdp_dbus_impl_permission_store_call_set_permission_sync (permission_store, - "inhibit", - TRUE, - "inhibit", - "", - permissions, - NULL, - &error); - g_assert_no_error (error); -} - -static void -unset_inhibit_permissions (void) -{ - xdp_dbus_impl_permission_store_call_delete_sync (permission_store, - "inhibit", - "inhibit", - NULL, - NULL); - /* Ignore the error here, since this fails if the table doesn't exist */ -} - -static int got_info; -static int inhibit_id[3]; - -static void -inhibit_cb (GObject *object, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (object); - GKeyFile *keyfile = data; - g_autoptr(GError) error = NULL; - int response; - int id; - - g_debug ("Got inhibit callback"); - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - - id = xdp_portal_session_inhibit_finish (portal, result, &error); - - if (response == 0) - { - g_assert_no_error (error); - g_assert_cmpint (id, >, 0); - } - else - { - g_assert_nonnull (error); - g_assert_cmpint (id, ==, -1); - } - - g_assert (0 <= got_info && got_info < 3); - inhibit_id[got_info] = id; - - got_info++; - g_main_context_wakeup (NULL); -} - -void -test_inhibit_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - XdpInhibitFlags flags; - const char *perms[] = { "logout", "suspend", NULL }; - - set_inhibit_permissions (perms); - unset_inhibit_permissions (); - - keyfile = g_key_file_new (); - - flags = XDP_INHIBIT_FLAG_LOGOUT|XDP_INHIBIT_FLAG_USER_SWITCH; - - g_key_file_set_integer (keyfile, "inhibit", "flags", flags); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "inhibit", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_session_inhibit (portal, NULL, "Testing portals", flags, NULL, inhibit_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - xdp_portal_session_uninhibit (portal, inhibit_id[0]); -} - -void -test_inhibit_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - XdpInhibitFlags flags; - - unset_inhibit_permissions (); - - keyfile = g_key_file_new (); - - flags = XDP_INHIBIT_FLAG_USER_SWITCH|XDP_INHIBIT_FLAG_IDLE; - - g_key_file_set_integer (keyfile, "inhibit", "flags", flags); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "inhibit", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_session_inhibit (portal, NULL, "Testing portals", flags, NULL, inhibit_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - xdp_portal_session_uninhibit (portal, inhibit_id[0]); -} - -void -test_inhibit_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - XdpInhibitFlags flags; - - unset_inhibit_permissions (); - - keyfile = g_key_file_new (); - - flags = XDP_INHIBIT_FLAG_USER_SWITCH|XDP_INHIBIT_FLAG_IDLE; - - g_key_file_set_integer (keyfile, "inhibit", "flags", flags); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "inhibit", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_session_inhibit (portal, NULL, "Testing portals", flags, NULL, inhibit_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -static gboolean -cancel_call (gpointer data) -{ - GCancellable *cancellable = data; - - g_debug ("cancel call"); - g_cancellable_cancel (cancellable); - - return G_SOURCE_REMOVE; -} - -void -test_inhibit_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - XdpInhibitFlags flags; - g_autoptr(GCancellable) cancellable = NULL; - - unset_inhibit_permissions (); - - keyfile = g_key_file_new (); - - flags = XDP_INHIBIT_FLAG_USER_SWITCH|XDP_INHIBIT_FLAG_IDLE; - - g_key_file_set_integer (keyfile, "inhibit", "flags", flags); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - - path = g_build_filename (outdir, "inhibit", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_session_inhibit (portal, NULL, "Testing portals", flags, cancellable, inhibit_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_inhibit_permissions (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - XdpInhibitFlags flags; - const char *permissions[] = { "logout", "suspend", NULL }; - - set_inhibit_permissions (permissions); - - keyfile = g_key_file_new (); - - flags = XDP_INHIBIT_FLAG_LOGOUT|XDP_INHIBIT_FLAG_USER_SWITCH; - - g_key_file_set_integer (keyfile, "inhibit", "flags", XDP_INHIBIT_FLAG_LOGOUT); /* user switch is not allowed */ - - path = g_build_filename (outdir, "inhibit", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_session_inhibit (portal, NULL, "Testing portals", flags, NULL, inhibit_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - xdp_portal_session_uninhibit (portal, inhibit_id[0]); - - unset_inhibit_permissions (); -} - -void -test_inhibit_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - XdpInhibitFlags flags; - - unset_inhibit_permissions (); - - keyfile = g_key_file_new (); - - flags = XDP_INHIBIT_FLAG_USER_SWITCH|XDP_INHIBIT_FLAG_IDLE; - - g_key_file_set_integer (keyfile, "inhibit", "flags", flags); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "inhibit", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_session_inhibit (portal, NULL, "Testing portals", flags, NULL, inhibit_cb, keyfile); - xdp_portal_session_inhibit (portal, NULL, "Testing portals", flags, NULL, inhibit_cb, keyfile); - xdp_portal_session_inhibit (portal, NULL, "Testing portals", flags, NULL, inhibit_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); - - xdp_portal_session_uninhibit (portal, inhibit_id[0]); - xdp_portal_session_uninhibit (portal, inhibit_id[1]); - xdp_portal_session_uninhibit (portal, inhibit_id[2]); -} - -/* tests below test session state monitoring */ - -static void -monitor_cb (GObject *object, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (object); - g_autoptr(GError) error = NULL; - gboolean ret; - - ret = xdp_portal_session_monitor_start_finish (portal, result, &error); - g_assert_true (ret); - g_assert_no_error (error); - - got_info += 1; -} - -static void -session_state_changed_cb (XdpPortal *portal, - gboolean screensaver_active, - XdpLoginSessionState state, - gpointer data) -{ - g_assert_false (screensaver_active); - g_assert_cmpint (state, ==, XDP_LOGIN_SESSION_RUNNING); - - got_info += 1; -} - -static void -session_state_changed_cb2 (XdpPortal *portal, - gboolean screensaver_active, - XdpLoginSessionState state, - gpointer data) -{ - g_assert_false (screensaver_active); - g_assert_cmpint (state, ==, XDP_LOGIN_SESSION_QUERY_END); - - got_info += 1; -} - -static gboolean -bump_got_info (gpointer data) -{ - got_info += 1; - - return G_SOURCE_REMOVE; -} - -void -test_inhibit_monitor (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - gulong id; - - if (g_getenv ("TEST_IN_CI")) - { - g_test_skip ("Skip tests that are unreliable in CI"); - return; - } - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 1000); - g_key_file_set_string (keyfile, "backend", "change", "query-end"); - - path = g_build_filename (outdir, "inhibit", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - id = g_signal_connect (portal, "session-state-changed", G_CALLBACK (session_state_changed_cb), NULL); - - got_info = 0; - xdp_portal_session_monitor_start (portal, NULL, 0, NULL, monitor_cb, NULL); - - /* we get a monitor_cb and an initial state-changed emission */ - while (got_info < 2) - g_main_context_iteration (NULL, TRUE); - - g_signal_handler_disconnect (portal, id); - - /* now wait for the query-end state */ - g_debug ("waiting for query-end state\n"); - got_info = 0; - g_signal_connect (portal, "session-state-changed", G_CALLBACK (session_state_changed_cb2), NULL); - - while (got_info < 1) - g_main_context_iteration (NULL, TRUE); - - xdp_portal_session_monitor_stop (portal); - - /* after calling stop, no more state-changed signals */ - got_info = 0; - g_timeout_add (500, bump_got_info, NULL); - while (got_info < 1) - g_main_context_iteration (NULL, TRUE); -} diff --git a/tests/inhibit.h b/tests/inhibit.h deleted file mode 100644 index fca8292..0000000 --- a/tests/inhibit.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -void test_inhibit_basic (void); -void test_inhibit_delay (void); -void test_inhibit_close (void); -void test_inhibit_cancel (void); -void test_inhibit_parallel (void); -void test_inhibit_permissions (void); - -void test_inhibit_monitor (void); diff --git a/tests/limited-portals.c b/tests/limited-portals.c deleted file mode 100644 index f9e13ee..0000000 --- a/tests/limited-portals.c +++ /dev/null @@ -1,494 +0,0 @@ -#include "config.h" - -#include -#include - -#include - -#include "src/glib-backports.h" -#include "xdp-dbus.h" -#include "xdp-utils.h" -#include "xdp-impl-dbus.h" - -#ifdef HAVE_LIBPORTAL -#include "account.h" -#include "background.h" -#include "camera.h" -#include "email.h" -#include "filechooser.h" -#include "inhibit.h" -#include "location.h" -#include "notification.h" -#include "openuri.h" -#include "print.h" -#include "screenshot.h" -#include "trash.h" -#include "wallpaper.h" -#endif - -#include "utils.h" - -/* required while we support meson + autotools. Autotools builds everything in - the root dir ('.'), meson builds in each subdir nested and overrides these for - g_test_build_filename */ -#ifndef XDG_DP_BUILDDIR -#define XDG_DP_BUILDDIR "." -#endif -#ifndef XDG_PS_BUILDDIR -#define XDG_PS_BUILDDIR "." -#endif - -#define PORTAL_BUS_NAME "org.freedesktop.portal.Desktop" -#define PORTAL_OBJECT_PATH "/org/freedesktop/portal/desktop" -#define BACKEND_BUS_NAME "org.freedesktop.impl.portal.Limited" -#define BACKEND_OBJECT_PATH "/org/freedesktop/portal/desktop" - -#include "document-portal/permission-store-dbus.h" - -char outdir[] = "/tmp/xdp-test-XXXXXX"; - -static GTestDBus *dbus; -static GDBusConnection *session_bus; -static GList *test_procs = NULL; -XdpDbusImplPermissionStore *permission_store; -XdpDbusImplLockdown *lockdown; - -int -xdup (int oldfd) -{ - int newfd = dup (oldfd); - - if (newfd < 0) - g_error ("Unable to duplicate fd %d: %s", oldfd, g_strerror (errno)); - - return newfd; -} - -static void -name_appeared_cb (GDBusConnection *bus, - const char *name, - const char *name_owner, - gpointer data) -{ - gboolean *b = (gboolean *)data; - - g_debug ("Name %s now owned by %s\n", name, name_owner); - - *b = TRUE; - - g_main_context_wakeup (NULL); -} - -static void -name_disappeared_cb (GDBusConnection *bus, - const char *name, - gpointer data) -{ - g_debug ("Name %s disappeared\n", name); -} - -static gboolean -timeout_cb (gpointer data) -{ - const char *msg = data; - - g_error ("%s", msg); - - return G_SOURCE_REMOVE; -} - -static void -update_data_dirs (void) -{ - const char *data_dirs; - gssize len = 0; - g_autoptr(GString) str = NULL; - - data_dirs = g_getenv ("XDG_DATA_DIRS"); - if (data_dirs != NULL && - strstr (data_dirs, "/usr/share") != NULL) - { - return; - } - - if (data_dirs != NULL) - { - len = strlen (data_dirs); - if (data_dirs[len] == ':') - len--; - } - - str = g_string_new_len (data_dirs, len); - if (str->len > 0) - g_string_append_c (str, ':'); - g_string_append (str, "/usr/local/share/:/usr/share/"); - - g_debug ("Setting XDG_DATA_DIRS to %s", str->str); - g_setenv ("XDG_DATA_DIRS", str->str, TRUE); -} - -static void -global_setup (void) -{ - GError *error = NULL; - g_autofree gchar *backends_executable = NULL; - g_autofree gchar *services = NULL; - g_autofree gchar *portal_dir = NULL; - g_autofree gchar *argv0 = NULL; - g_autoptr(GSubprocessLauncher) launcher = NULL; - g_autoptr(GSubprocess) subprocess = NULL; - guint name_timeout; - const char *argv[4]; - GQuark portal_errors G_GNUC_UNUSED; - static gboolean name_appeared; - guint watch; - guint timeout_mult = 1; - - update_data_dirs (); - - g_mkdtemp (outdir); - g_debug ("outdir: %s\n", outdir); - - g_setenv ("XDG_CURRENT_DESKTOP", "limited", TRUE); - g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE); - g_setenv ("XDG_DATA_HOME", outdir, TRUE); - - /* Re-defining dbus-daemon with a custom script */ - setup_dbus_daemon_wrapper (outdir); - - dbus = g_test_dbus_new (G_TEST_DBUS_NONE); - services = g_test_build_filename (G_TEST_BUILT, "services", NULL); - g_test_dbus_add_service_dir (dbus, services); - g_test_dbus_up (dbus); - - if (g_getenv ("TEST_IN_CI")) - timeout_mult = 10; - - /* g_test_dbus_up unsets this, so re-set */ - g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE); - - session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); - g_assert_no_error (error); - - /* start portal backends */ - name_appeared = FALSE; - watch = g_bus_watch_name_on_connection (session_bus, - BACKEND_BUS_NAME, - 0, - name_appeared_cb, - name_disappeared_cb, - &name_appeared, - NULL); - - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); - g_subprocess_launcher_setenv (launcher, "G_DEBUG", "fatal-criticals", TRUE); - g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", g_test_dbus_get_bus_address (dbus), TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DATA_HOME", outdir, TRUE); - g_subprocess_launcher_setenv (launcher, "PATH", g_getenv ("PATH"), TRUE); - g_subprocess_launcher_take_stdout_fd (launcher, xdup (STDERR_FILENO)); - - backends_executable = g_test_build_filename (G_TEST_BUILT, "test-backends", NULL); - argv[0] = backends_executable; - argv[1] = "--backend-name=" BACKEND_BUS_NAME; - argv[2] = g_test_verbose () ? "--verbose" : NULL; - argv[3] = NULL; - - g_debug ("launching test-backend\n"); - - subprocess = g_subprocess_launcher_spawnv (launcher, argv, &error); - g_assert_no_error (error); - g_test_message ("Launched %s with pid %s\n", argv[0], - g_subprocess_get_identifier (subprocess)); - test_procs = g_list_append (test_procs, g_steal_pointer (&subprocess)); - - name_timeout = g_timeout_add (1000 * timeout_mult, timeout_cb, "Failed to launch test-backends"); - - while (!name_appeared) - g_main_context_iteration (NULL, TRUE); - - g_source_remove (name_timeout); - g_bus_unwatch_name (watch); - - /* start permission store */ - name_appeared = FALSE; - watch = g_bus_watch_name_on_connection (session_bus, - "org.freedesktop.impl.portal.PermissionStore", - 0, - name_appeared_cb, - name_disappeared_cb, - &name_appeared, - NULL); - - g_clear_object (&launcher); - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); - g_subprocess_launcher_setenv (launcher, "G_DEBUG", "fatal-criticals", TRUE); - g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", g_test_dbus_get_bus_address (dbus), TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DATA_HOME", outdir, TRUE); - g_subprocess_launcher_setenv (launcher, "PATH", g_getenv ("PATH"), TRUE); - g_subprocess_launcher_take_stdout_fd (launcher, xdup (STDERR_FILENO)); - - if (g_getenv ("XDP_UNINSTALLED") != NULL) - argv0 = g_test_build_filename (G_TEST_BUILT, "..", XDG_PS_BUILDDIR, "xdg-permission-store", NULL); - else - argv0 = g_strdup (LIBEXECDIR "/xdg-permission-store"); - - argv[0] = argv0; - argv[1] = "--replace"; - argv[2] = g_test_verbose () ? "--verbose" : NULL; - argv[3] = NULL; - - g_debug ("launching %s\n", argv0); - - subprocess = g_subprocess_launcher_spawnv (launcher, argv, &error); - g_assert_no_error (error); - g_test_message ("Launched %s with pid %s\n", argv[0], - g_subprocess_get_identifier (subprocess)); - test_procs = g_list_append (test_procs, g_steal_pointer (&subprocess)); - - name_timeout = g_timeout_add (1000 * timeout_mult, timeout_cb, "Failed to launch xdg-permission-store"); - - while (!name_appeared) - g_main_context_iteration (NULL, TRUE); - - g_source_remove (name_timeout); - g_bus_unwatch_name (watch); - - /* start portals */ - name_appeared = FALSE; - watch = g_bus_watch_name_on_connection (session_bus, - PORTAL_BUS_NAME, - 0, - name_appeared_cb, - name_disappeared_cb, - &name_appeared, - NULL); - - portal_dir = g_test_build_filename (G_TEST_BUILT, "portals", "limited", NULL); - - g_clear_object (&launcher); - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); - g_subprocess_launcher_setenv (launcher, "G_DEBUG", "fatal-criticals", TRUE); - g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", g_test_dbus_get_bus_address (dbus), TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DESKTOP_PORTAL_DIR", portal_dir, TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DATA_HOME", outdir, TRUE); - g_subprocess_launcher_setenv (launcher, "PATH", g_getenv ("PATH"), TRUE); - g_subprocess_launcher_take_stdout_fd (launcher, xdup (STDERR_FILENO)); - - g_clear_pointer (&argv0, g_free); - - if (g_getenv ("XDP_UNINSTALLED") != NULL) - argv0 = g_test_build_filename (G_TEST_BUILT, "..", XDG_DP_BUILDDIR, "xdg-desktop-portal", NULL); - else - argv0 = g_strdup (LIBEXECDIR "/xdg-desktop-portal"); - - argv[0] = argv0; - argv[1] = g_test_verbose () ? "--verbose" : NULL; - argv[2] = NULL; - - g_debug ("launching %s\n", argv0); - - subprocess = g_subprocess_launcher_spawnv (launcher, argv, &error); - g_assert_no_error (error); - g_test_message ("Launched %s with pid %s\n", argv[0], - g_subprocess_get_identifier (subprocess)); - test_procs = g_list_append (test_procs, g_steal_pointer (&subprocess)); - g_clear_pointer (&argv0, g_free); - - name_timeout = g_timeout_add (1000 * timeout_mult, timeout_cb, "Failed to launch xdg-desktop-portal"); - - while (!name_appeared) - g_main_context_iteration (NULL, TRUE); - - g_source_remove (name_timeout); - g_bus_unwatch_name (watch); - - permission_store = xdp_dbus_impl_permission_store_proxy_new_sync (session_bus, - 0, - "org.freedesktop.impl.portal.PermissionStore", - "/org/freedesktop/impl/portal/PermissionStore", - NULL, - &error); - g_assert_no_error (error); - - lockdown = xdp_dbus_impl_lockdown_proxy_new_sync (session_bus, - 0, - BACKEND_BUS_NAME, - BACKEND_OBJECT_PATH, - NULL, - &error); - g_assert_no_error (error); - - /* make sure errors are registered */ - portal_errors = XDG_DESKTOP_PORTAL_ERROR; -} - -static void -wait_for_test_procs (void) -{ - GList *l; - - for (l = test_procs; l; l = l->next) - { - GSubprocess *subprocess = G_SUBPROCESS (l->data); - GError *error = NULL; - g_autofree char *identifier = NULL; - - identifier = g_strdup (g_subprocess_get_identifier (subprocess)); - - g_debug ("Terminating and waiting for process %s", identifier); - g_subprocess_send_signal (subprocess, SIGTERM); - - /* This may lead the test to hang, we assume that the test suite or CI - * can handle the case at upper level, without having us async function - * and timeouts */ - g_subprocess_wait (subprocess, NULL, &error); - g_assert_no_error (error); - g_assert_null (g_subprocess_get_identifier (subprocess)); - - if (!g_subprocess_get_if_exited (subprocess)) - { - g_assert_true (g_subprocess_get_if_signaled (subprocess)); - g_assert_cmpint (g_subprocess_get_term_sig (subprocess), ==, SIGTERM); - } - else if (!g_subprocess_get_successful (subprocess)) - { - g_error ("Subprocess %s, exited with exit status %d", identifier, - g_subprocess_get_exit_status (subprocess)); - } - } -} - -static void -global_teardown (void) -{ - GError *error = NULL; - - g_dbus_connection_flush_sync (session_bus, NULL, &error); - g_assert_no_error (error); - - g_dbus_connection_close_sync (session_bus, NULL, &error); - g_assert_no_error (error); - - wait_for_test_procs (); - g_list_free_full (g_steal_pointer (&test_procs), g_object_unref); - - g_object_unref (lockdown); - g_object_unref (permission_store); - - g_object_unref (session_bus); - - g_test_dbus_down (dbus); - - g_object_unref (dbus); -} - -/* Just check that the portal is there, and has the - * expected version. This will fail if the backend - * is not found. - */ -#define DEFINE_TEST_EXISTS(pp,PP,version) \ -static void \ -test_##pp##_exists (void) \ -{ \ - g_autoptr(GDBusProxy) proxy = NULL; \ - g_autoptr(GError) error = NULL; \ - g_autofree char *owner = NULL; \ - \ - proxy = G_DBUS_PROXY (xdp_dbus_##pp##_proxy_new_sync (session_bus, \ - 0, \ - PORTAL_BUS_NAME, \ - PORTAL_OBJECT_PATH, \ - NULL, \ - &error)); \ - g_assert_no_error (error); \ - \ - owner = g_dbus_proxy_get_name_owner (proxy); \ - g_assert_nonnull (owner); \ - \ - g_assert_cmpuint (xdp_dbus_##pp##_get_version (XDP_DBUS_##PP (proxy)), ==, version); \ -} - -/* Just check that the portal is not there. - * - * We do a version check, but we hardcode the default value of zero, - * as all portals will have a version greater than, or equal to one. - */ -#define DEFINE_TEST_DOES_NOT_EXIST(pp,PP) \ -static void \ -test_##pp##_does_not_exist (void) \ -{ \ - g_autoptr(GDBusProxy) proxy = NULL; \ - g_autoptr(GError) error = NULL; \ - g_autofree char *owner = NULL; \ - \ - proxy = G_DBUS_PROXY (xdp_dbus_##pp##_proxy_new_sync (session_bus, \ - 0, \ - PORTAL_BUS_NAME, \ - PORTAL_OBJECT_PATH, \ - NULL, \ - &error)); \ - g_assert_no_error (error); \ - \ - owner = g_dbus_proxy_get_name_owner (proxy); \ - g_assert_nonnull (owner); \ - \ - g_assert_cmpuint (xdp_dbus_##pp##_get_version (XDP_DBUS_##PP (proxy)), ==, 0); \ -} - -DEFINE_TEST_EXISTS(file_chooser, FILE_CHOOSER, 3) - -DEFINE_TEST_DOES_NOT_EXIST(print, PRINT) - -int -main (int argc, char **argv) -{ - int res; - - /* Better leak reporting without gvfs */ - g_setenv ("GIO_USE_VFS", "local", TRUE); - - g_log_writer_default_set_use_stderr (TRUE); - - setlocale (LC_ALL, NULL); - - g_test_init (&argc, &argv, NULL); - - g_test_add_func ("/limited/filechooser/exists", test_file_chooser_exists); - g_test_add_func ("/limited/print/does-not-exist", test_print_does_not_exist); - -#ifdef HAVE_LIBPORTAL - g_test_add_func ("/limited/openfile/basic", test_open_file_basic); - g_test_add_func ("/limited/openfile/delay", test_open_file_delay); - g_test_add_func ("/limited/openfile/close", test_open_file_close); - g_test_add_func ("/limited/openfile/cancel", test_open_file_cancel); - g_test_add_func ("/limited/openfile/multiple", test_open_file_multiple); - g_test_add_func ("/limited/openfile/filters1", test_open_file_filters1); - g_test_add_func ("/limited/openfile/filters2", test_open_file_filters2); - g_test_add_func ("/limited/openfile/current_filter1", test_open_file_current_filter1); - g_test_add_func ("/limited/openfile/current_filter2", test_open_file_current_filter2); - g_test_add_func ("/limited/openfile/current_filter3", test_open_file_current_filter3); - g_test_add_func ("/limited/openfile/current_filter4", test_open_file_current_filter4); - g_test_add_func ("/limited/openfile/choices1", test_open_file_choices1); - g_test_add_func ("/limited/openfile/choices2", test_open_file_choices2); - g_test_add_func ("/limited/openfile/choices3", test_open_file_choices3); - g_test_add_func ("/limited/openfile/parallel", test_open_file_parallel); - - g_test_add_func ("/limited/savefile/basic", test_save_file_basic); - g_test_add_func ("/limited/savefile/delay", test_save_file_delay); - g_test_add_func ("/limited/savefile/close", test_save_file_close); - g_test_add_func ("/limited/savefile/cancel", test_save_file_cancel); - g_test_add_func ("/limited/savefile/filters", test_save_file_filters); - g_test_add_func ("/limited/savefile/lockdown", test_save_file_lockdown); - g_test_add_func ("/limited/savefile/parallel", test_save_file_parallel); -#endif - - global_setup (); - - res = g_test_run (); - - sleep (1); - - global_teardown (); - - return res; -} diff --git a/tests/location.c b/tests/location.c deleted file mode 100644 index 8726bc6..0000000 --- a/tests/location.c +++ /dev/null @@ -1,107 +0,0 @@ -#include - -#include "location.h" - -#include "xdp-utils.h" -#include - -extern char outdir[]; - -static int got_info = 0; - -static void -location_cb (GObject *source, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (source); - g_autoptr(GError) error = NULL; - gboolean res; - - res = xdp_portal_location_monitor_start_finish (portal, result, &error); - g_assert_true (res); - g_assert_no_error (error); - - got_info = 1; -} - -void -test_location_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GDBusConnection) system_bus = NULL; - g_autoptr(GError) error = NULL; - -#ifndef HAVE_GEOCLUE - g_test_skip ("Skipping tests that require geoclue"); - return; -#endif - - system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); - - if (system_bus == NULL) - { - g_prefix_error (&error, "Unable to test Location without system bus: "); - g_test_skip (error->message); - return; - } - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_location_monitor_start (portal, NULL, 0, 0, XDP_LOCATION_ACCURACY_EXACT, 0, NULL, location_cb, NULL); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - xdp_portal_location_monitor_stop (portal); -} - -static void -location_error (GObject *source, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (source); - g_autoptr(GError) error = NULL; - gboolean res; - - res = xdp_portal_location_monitor_start_finish (portal, result, &error); - g_assert_false (res); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - - got_info = 1; -} - -void -test_location_accuracy (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GDBusConnection) system_bus = NULL; - g_autoptr(GError) error = NULL; - -#ifndef HAVE_GEOCLUE - g_test_skip ("Skipping tests that require geoclue"); - return; -#endif - - system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); - - if (system_bus == NULL) - { - g_prefix_error (&error, "Unable to test Location without system bus: "); - g_test_skip (error->message); - return; - } - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_location_monitor_start (portal, NULL, 0, 0, 22, 0, NULL, location_error, NULL); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - xdp_portal_location_monitor_stop (portal); -} - diff --git a/tests/location.h b/tests/location.h deleted file mode 100644 index 0f089f5..0000000 --- a/tests/location.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -void test_location_basic (void); -void test_location_accuracy (void); diff --git a/tests/meson.build b/tests/meson.build index a2dafee..87b2e39 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,9 +1,9 @@ env_tests = environment() -env_tests.set('XDP_UNINSTALLED', '1') env_tests.set('XDG_DATA_DIRS', meson.current_build_dir() / 'share') env_tests.set('G_TEST_SRCDIR', meson.current_source_dir()) env_tests.set('G_TEST_BUILDDIR', meson.current_build_dir()) env_tests.set('G_DEBUG', 'gc-friendly') # from glib-tap.mk +env_tests.set('LSAN_OPTIONS', 'suppressions=' + meson.current_source_dir() / 'asan.suppression') if glib_dep.version().version_compare('>= 2.68') test_protocol = 'tap' @@ -12,362 +12,180 @@ else endif subdir('dbs') -subdir('portals') -subdir('services') -subdir('share') - -test_db = executable( - 'testdb', - ['testdb.c'] + db_sources, +test_permission_db = executable( + 'test-permission-db', + ['test-permission-db.c'] + db_sources, dependencies: [common_deps], include_directories: [common_includes], install: enable_installed_tests, install_dir: installed_tests_dir, ) test( - 'testdb', - test_db, + 'unit/permission-db', + test_permission_db, + suite: ['unit'], env: env_tests, is_parallel: false, protocol: test_protocol, ) -if enable_installed_tests - install_data('dbs/no_tables', install_dir: installed_tests_dir / 'dbs') -endif - -test_doc_portal = executable( - 'test-doc-portal', - 'can-use-fuse.c', - 'test-doc-portal.c', - 'utils.c', - document_portal_built_sources, - dependencies: [common_deps, fuse3_dep], - include_directories: [common_includes], +test_xdp_utils = executable( + 'test-xdp-utils', + 'test-xdp-utils.c', + xdp_utils_sources, + dependencies: [common_deps, xdp_utils_deps], + include_directories: [common_includes, xdp_utils_includes], install: enable_installed_tests, install_dir: installed_tests_dir, ) test( - 'test-doc-portal', - test_doc_portal, + 'unit/xdp-utils', + test_xdp_utils, + suite: ['unit'], env: env_tests, is_parallel: false, protocol: test_protocol, ) -test_backends_sources = files( - 'backend/test-backends.c', - 'backend/request.c', - 'backend/session.c', - 'backend/access.c', - 'backend/account.c', - 'backend/appchooser.c', - 'backend/background.c', - 'backend/email.c', - 'backend/filechooser.c', - 'backend/inhibit.c', - 'backend/lockdown.c', - 'backend/notification.c', - 'backend/print.c', - 'backend/screenshot.c', - 'backend/settings.c', - 'backend/wallpaper.c', - 'glib-backports.c', -) - -# We build this in the tests/ subdirectory so that it can be accessed -# via G_TEST_BUILT -test_backends = executable( - 'test-backends', - test_backends_sources, - document_portal_built_sources, - document_portal_built_sources, - impl_built_sources, +test_method_info = executable( + 'test-xdp-method-info', + 'test-xdp-method-info.c', + xdp_method_info_sources, dependencies: [common_deps], - include_directories: [common_includes, src_includes], + include_directories: [common_includes, xdp_utils_includes], install: enable_installed_tests, install_dir: installed_tests_dir, ) - -test_portals_sources = files( - 'test-portals.c', - 'utils.c', +test( + 'unit/xdp-method-info', + test_method_info, + suite: ['unit'], + env: env_tests, + is_parallel: true, + protocol: test_protocol, ) -limited_portals_sources = files( - 'limited-portals.c', - 'utils.c', -) +run_test = find_program('run-test.sh') -if have_libportal - extra_portals_sources = files( - 'account.c', - 'background.c', - 'camera.c', - 'email.c', - 'filechooser.c', - 'inhibit.c', - 'location.c', - 'notification.c', - 'openuri.c', - 'print.c', - 'screenshot.c', - 'trash.c', - 'wallpaper.c', - 'glib-backports.c', - ) +pytest_args = ['--verbose', '--log-level=DEBUG'] - test_portals_sources += extra_portals_sources - limited_portals_sources += extra_portals_sources -endif +pytest_env = environment() +pytest_env.set('BUILDDIR', meson.project_build_root()) -test_portals = executable( - 'test-portals', - impl_built_sources, - permission_store_built_sources, - portal_built_sources, - sd_escape_sources, - test_portals_sources, - xdp_utils_sources, - dependencies: [common_deps, libportal_dep, libsystemd_dep], - include_directories: [common_includes, xdp_utils_includes], - c_args: [ - '-DXDG_DP_BUILDDIR="src"', - '-DXDG_PS_BUILDDIR="document-portal"', - ], - install: enable_installed_tests, - install_dir: installed_tests_dir, -) - -limited_portals = executable( - 'limited-portals', - impl_built_sources, - permission_store_built_sources, - portal_built_sources, - sd_escape_sources, - limited_portals_sources, - xdp_utils_sources, - dependencies: [common_deps, libportal_dep, libsystemd_dep], - include_directories: [common_includes, xdp_utils_includes], - c_args: [ - '-DXDG_DP_BUILDDIR="src"', - '-DXDG_PS_BUILDDIR="document-portal"', - ], - install: enable_installed_tests, - install_dir: installed_tests_dir, -) +# pytest xdist is nice because it significantly speeds up our +# test process, but it's not required +if pymod.find_installation('python3', modules: ['xdist'], required: false).found() + # using auto can easily start too many tests which will block each other + # a value of around 5 seems to work well + pytest_args += ['-n', '5'] +endif -# Split the portal tests into one test per portal, this makes debugging a lot -# easier. -# Keep in sync with test-portals.c -portal_tests = [ - 'account', - 'background', - 'camera', - 'color', - 'email', - 'inhibit', - 'location', - 'notification', - 'openfile', - 'openuri', - 'prepareprint', - 'print', - 'savefile', - 'screenshot', - 'trash', - 'wallpaper', +pytest_files = [ + 'test_account.py', + 'test_background.py', + 'test_camera.py', + 'test_clipboard.py', + 'test_documents.py', + 'test_document_fuse.py', + 'test_dynamiclauncher.py', + 'test_email.py', + 'test_filechooser.py', + 'test_globalshortcuts.py', + 'test_inhibit.py', + 'test_inputcapture.py', + 'test_location.py', + 'test_notification.py', + 'test_openuri.py', + 'test_permission_store.py', + 'test_print.py', + 'test_registry.py', + 'test_remotedesktop.py', + 'test_settings.py', + 'test_screenshot.py', + 'test_trash.py', + 'test_usb.py', + 'test_wallpaper.py', ] -test_env = env_tests -test_env.set('XDG_CURRENT_DESKTOP', 'test') -foreach p : portal_tests - test( - 'test-portals-@0@'.format(p), - test_portals, - args: ['--verbose', '--keep-going', '--tap', '-p', '/portal/@0@'.format(p)], - depends: [test_backends, test_portals], - env: test_env, - is_parallel: false, - protocol: test_protocol, - suite: 'portals', - ) -endforeach - -# Split the portal tests into one test per portal, this makes debugging a lot -# easier. -# Keep in sync with test-portals.c -portal_limited = [ - 'openfile', - 'savefile', +template_files = [ + 'templates/access.py', + 'templates/account.py', + 'templates/appchooser.py', + 'templates/background.py', + 'templates/clipboard.py', + 'templates/dynamiclauncher.py', + 'templates/email.py', + 'templates/filechooser.py', + 'templates/geoclue2.py', + 'templates/globalshortcuts.py', + 'templates/inhibit.py', + 'templates/__init__.py', + 'templates/inputcapture.py', + 'templates/lockdown.py', + 'templates/notification.py', + 'templates/print.py', + 'templates/remotedesktop.py', + 'templates/screenshot.py', + 'templates/settings.py', + 'templates/usb.py', + 'templates/wallpaper.py', ] -limited_env = env_tests -limited_env.set('XDG_CURRENT_DESKTOP', 'limited') -foreach p : portal_limited - test( - 'limited-portals-@0@'.format(p), - limited_portals, - args: ['--verbose', '--keep-going', '--tap', '-p', '/limited/@0@'.format(p)], - depends: [test_backends, limited_portals], - env: limited_env, - is_parallel: false, - protocol: test_protocol, - suite: 'portals', - ) +foreach pytest_file : pytest_files + testname = pytest_file.replace('.py', '').replace('test_', '') + test( + 'integration/@0@'.format(testname), + run_test, + args: [meson.current_source_dir() / pytest_file] + pytest_args, + env: pytest_env, + suite: ['integration'], + timeout: 120, + ) endforeach if enable_installed_tests install_data( - 'session.conf.in', - 'test-document-fuse.sh', - 'test-document-fuse.py', - install_dir: installed_tests_dir + pytest_files, + '__init__.py', + 'conftest.py', + 'asan.suppression', + install_dir: installed_tests_dir / 'tests', + ) + install_data( + template_files, + install_dir: installed_tests_dir / 'tests' / 'templates', ) -endif - -test_permission_store = executable( - 'test-permission-store', - 'test-permission-store.c', - 'utils.c', - permission_store_built_sources, - xdp_utils_sources, - sd_escape_sources, - dependencies: [common_deps, libsystemd_dep], - include_directories: [common_includes, xdp_utils_includes], - install: enable_installed_tests, - install_dir: installed_tests_dir, -) -test( - 'test-permission-store', - test_permission_store, - env: env_tests, - is_parallel: false, - protocol: test_protocol, -) - -test_xdp_utils = executable( - 'test-xdp-utils', - 'test-xdp-utils.c', - 'utils.c', - xdp_utils_sources, - sd_escape_sources, - dependencies: [common_deps, libsystemd_dep], - include_directories: [common_includes, xdp_utils_includes], - install: enable_installed_tests, - install_dir: installed_tests_dir, -) -test( - 'test-xdp-utils', - test_xdp_utils, - env: env_tests, - is_parallel: false, - protocol: test_protocol, -) - -pytest = find_program('pytest-3', 'pytest', required: get_option('pytest')) -pymod = import('python') -python = pymod.find_installation( - 'python3', - modules: ['dbus', 'dbusmock', 'gi'], - required: get_option('pytest'), -) -enable_pytest = pytest.found() and python.found() and python.language_version().version_compare('>=3.9') + installed_env = { + 'XDG_DESKTOP_PORTAL_PATH': libexecdir / 'xdg-desktop-portal', + 'XDG_PERMISSION_STORE_PATH': libexecdir / 'xdg-permission-store', + 'XDG_DOCUMENT_PORTAL_PATH': libexecdir / 'xdg-document-portal', + 'XDP_VALIDATE_AUTO': '1', + } + env = '' + foreach key, value : installed_env + env += f'@key@=@value@ ' + endforeach -if enable_pytest - subdir('templates') + foreach pytest_file : pytest_files + testname = pytest_file.replace('.py', '').replace('test_', '') - pytest_args = ['--verbose', '--log-level=DEBUG'] + exec = [pytest.full_path(), installed_tests_dir / 'tests' / pytest_file] + exec += pytest_args + exec += ['-p', 'no:cacheprovider'] + exec = ' '.join(exec) - # pytest xdist is nice because it significantly speeds up our - # test process, but it's not required - if pymod.find_installation('python3', modules: ['xdist'], required: false).found() - pytest_args += ['-n', 'auto'] - endif + data = configuration_data() + data.set('exec', exec) + data.set('env', env) + data.set('libdir', libdir) - pytest_files = [ - 'conftest.py', - '__init__.py', - 'test_clipboard.py', - 'test_email.py', - 'test_globalshortcuts.py', - 'test_inputcapture.py', - 'test_remotedesktop.py', - 'test_trash.py', - ] - foreach pytest_file : pytest_files configure_file( - input: pytest_file, - output: pytest_file, - copy: true, - install: false + input: 'template.test.in', + output: 'integration-@0@.test'.format(testname), + configuration: data, + install: true, + install_dir: installed_tests_data_dir, ) - - if pytest_file.startswith('test_') - testname = pytest_file.replace('.py', '') - test( - 'pytest @0@'.format(testname), - pytest, - args: pytest_args + ['-k', testname], - suite: ['pytest'], - timeout: 120, - ) - endif - endforeach -endif - -if enable_installed_tests - # autotools used to symlink to the host files, here we just install our version - install_data( - doc_portal_service_file, - permission_portal_service_file, - install_dir: installed_tests_dir / 'services', - ) - - testfiles = [ - 'testdb', - 'test-doc-portal', - 'test-document-fuse.sh', - 'test-permission-store', - 'test-xdp-utils', - ] - foreach tf : testfiles - data = configuration_data() - data.set('installed_testdir', installed_tests_dir) - data.set('exec', tf) - configure_file( - input: 'template.test.in', - output: '@0@.test'.format(tf), - configuration: data, - install: true, - install_dir: installed_tests_data_dir, - ) endforeach - - foreach p : portal_tests - data = configuration_data() - data.set('installed_testdir', installed_tests_dir) - data.set('exec', 'test-portals -p /portal/@0@'.format(p)) - configure_file( - input: 'template.test.in', - output: 'test-portals-@0@.test'.format(p), - configuration: data, - install: true, - install_dir: installed_tests_data_dir, - ) - endforeach - - foreach p : portal_limited - data = configuration_data() - data.set('installed_testdir', installed_tests_dir) - data.set('exec', 'test-portals -p /limited/@0@'.format(p)) - configure_file( - input: 'template.test.in', - output: 'test-limited-@0@.test'.format(p), - configuration: data, - install: true, - install_dir: installed_tests_data_dir, - ) - endforeach -endif +endif \ No newline at end of file diff --git a/tests/notification.c b/tests/notification.c deleted file mode 100644 index d47c953..0000000 --- a/tests/notification.c +++ /dev/null @@ -1,256 +0,0 @@ - -#include - -#include "account.h" - -#include -#include "xdp-utils.h" - -extern char outdir[]; - -static int got_info; - -static void -notification_action_invoked (XdpPortal *portal, - const char *id, - const char *action, - GVariant *parameter, - gpointer data) -{ - GKeyFile *keyfile = data; - g_autofree char *exp_id = NULL; - g_autofree char *exp_action = NULL; - - exp_id = g_key_file_get_string (keyfile, "notification", "id", NULL); - exp_action = g_key_file_get_string (keyfile, "notification", "action", NULL); - - g_assert_cmpstr (exp_id, ==, id); - g_assert_cmpstr (exp_action, ==, action); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_notification_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GVariant) notification = NULL; - const char *notification_s; - gulong id; - - notification_s = "{ 'title': <'title'>, " - " 'body': <'test notification body'>, " - " 'priority': <'normal'>, " - " 'default-action': <'test-action'> }"; - - notification = g_variant_parse (G_VARIANT_TYPE_VARDICT, notification_s, NULL, NULL, NULL); - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "notification", "data", notification_s); - g_key_file_set_string (keyfile, "notification", "id", "test"); - g_key_file_set_string (keyfile, "notification", "action", "test-action"); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - - path = g_build_filename (outdir, "notification", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - id = g_signal_connect (portal, "notification-action-invoked", G_CALLBACK (notification_action_invoked), keyfile); - - got_info = 0; - xdp_portal_add_notification (portal, "test", notification, 0, NULL, NULL, NULL); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - g_signal_handler_disconnect (portal, id); - - xdp_portal_remove_notification (portal, "test"); -} - -void -test_notification_buttons (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GVariant) notification = NULL; - const char *notification_s; - gulong id; - - notification_s = "{ 'title': <'test notification 2'>, " - " 'body': <'test notification body 2'>, " - " 'priority': <'low'>, " - " 'default-action': <'test-action'>, " - " 'buttons': <[{'label': <'button1'>, 'action': <'action1'>}, " - " {'label': <'button2'>, 'action': <'action2'>}]> " - "}"; - - notification = g_variant_parse (G_VARIANT_TYPE_VARDICT, notification_s, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "notification", "data", notification_s); - g_key_file_set_string (keyfile, "notification", "id", "test2"); - g_key_file_set_string (keyfile, "notification", "action", "action1"); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - - path = g_build_filename (outdir, "notification", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - id = g_signal_connect (portal, "notification-action-invoked", G_CALLBACK (notification_action_invoked), keyfile); - - got_info = 0; - xdp_portal_add_notification (portal, "test2", notification, 0, NULL, NULL, NULL); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - g_signal_handler_disconnect (portal, id); - - xdp_portal_remove_notification (portal, "test2"); -} - -static void -notification_fail (GObject *source, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (source); - g_autoptr(GError) error = NULL; - gboolean res; - - res = xdp_portal_add_notification_finish (portal, result, &error); - g_assert_false (res); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT); - - got_info++; - g_main_context_wakeup (NULL); -} - -void -test_notification_bad_arg (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GVariant) notification = NULL; - const char *notification_s; - - notification_s = "{ 'title': <'test notification 3'>, " - " 'bodx': <'test notification body 3'> " - "}"; - - notification = g_variant_parse (G_VARIANT_TYPE_VARDICT, notification_s, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "notification", "data", notification_s); - g_key_file_set_string (keyfile, "notification", "id", "test2"); - g_key_file_set_string (keyfile, "notification", "action", "action1"); - g_key_file_set_boolean (keyfile, "backend", "expect-no-call", TRUE); - - path = g_build_filename (outdir, "notification", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_add_notification (portal, "test3", notification, 0, NULL, notification_fail, NULL); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_notification_bad_priority (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GVariant) notification = NULL; - const char *notification_s; - - notification_s = "{ 'title': <'test notification 2'>, " - " 'body': <'test notification body 2'>, " - " 'priority': <'invalid'> " - "}"; - - notification = g_variant_parse (G_VARIANT_TYPE_VARDICT, notification_s, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "notification", "data", notification_s); - g_key_file_set_string (keyfile, "notification", "id", "test2"); - g_key_file_set_string (keyfile, "notification", "action", "action1"); - g_key_file_set_boolean (keyfile, "backend", "expect-no-call", TRUE); - - path = g_build_filename (outdir, "notification", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_add_notification (portal, "test4", notification, 0, NULL, notification_fail, NULL); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_notification_bad_button (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GVariant) notification = NULL; - const char *notification_s; - - notification_s = "{ 'title': <'test notification 5'>, " - " 'body': <'test notification body 5'>, " - " 'buttons': <[{'labex': <'button1'>, 'action': <'action1'>}, " - " {'label': <'button2'>, 'action': <'action2'>}]> " - "}"; - - notification = g_variant_parse (G_VARIANT_TYPE_VARDICT, notification_s, NULL, NULL, &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "notification", "data", notification_s); - g_key_file_set_string (keyfile, "notification", "id", "test2"); - g_key_file_set_string (keyfile, "notification", "action", "action1"); - g_key_file_set_boolean (keyfile, "backend", "expect-no-call", TRUE); - - path = g_build_filename (outdir, "notification", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_add_notification (portal, "test5", notification, 0, NULL, notification_fail, NULL); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} diff --git a/tests/notification.h b/tests/notification.h deleted file mode 100644 index b22fbbf..0000000 --- a/tests/notification.h +++ /dev/null @@ -1,8 +0,0 @@ - -#pragma once - -void test_notification_basic (void); -void test_notification_buttons (void); -void test_notification_bad_arg (void); -void test_notification_bad_priority (void); -void test_notification_bad_button (void); diff --git a/tests/openuri.c b/tests/openuri.c deleted file mode 100644 index b78b37e..0000000 --- a/tests/openuri.c +++ /dev/null @@ -1,480 +0,0 @@ -#include - -#include "openuri.h" - -#include -#include "xdp-utils.h" -#include "xdp-impl-dbus.h" - -#include "utils.h" - -extern XdpDbusImplLockdown *lockdown; -extern XdpDbusImplPermissionStore *permission_store; - -extern char outdir[]; - -static int got_info = 0; - -static void -set_openuri_permissions (const char *type, - const char *handler, - guint count, - guint threshold) -{ - g_autoptr(GError) error = NULL; - g_autofree char *count_s = g_strdup_printf ("%u", count); - g_autofree char *threshold_s = g_strdup_printf ("%u", threshold); - const char *permissions[4]; - - permissions[0] = handler; - permissions[1] = count_s; - permissions[2] = threshold_s; - permissions[3] = NULL; - - xdp_dbus_impl_permission_store_call_delete_sync (permission_store, - "desktop-used-apps", - type, - NULL, - NULL); - - xdp_dbus_impl_permission_store_call_set_permission_sync (permission_store, - "desktop-used-apps", - TRUE, - type, - "", - permissions, - NULL, - &error); - g_assert_no_error (error); -} - -static void -unset_openuri_permissions (const char *type) -{ - xdp_dbus_impl_permission_store_call_delete_sync (permission_store, - "desktop-used-apps", - type, - NULL, - NULL); - /* Ignore the error here, since this fails if the table doesn't exist */ -} - -static void -enable_paranoid_mode (const char *type) -{ - GVariantBuilder data_builder; - - /* turn on paranoid mode to ensure we get a backend call */ - g_variant_builder_init (&data_builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&data_builder, "{sv}", "always-ask", g_variant_new_boolean (TRUE)); - xdp_dbus_impl_permission_store_call_set_value_sync (permission_store, - "desktop-used-apps", - TRUE, - type, - g_variant_new_variant (g_variant_builder_end (&data_builder)), - NULL, - NULL); -} - -static void -open_uri_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - GKeyFile *keyfile = data; - gboolean ret; - int response; - int domain; - int code; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - domain = g_key_file_get_integer (keyfile, "result", "error_domain", NULL); - code = g_key_file_get_integer (keyfile, "result", "error_code", NULL); - - ret = xdp_portal_open_uri_finish (portal, result, &error); - if (response == 0) - { - g_assert_no_error (error); - g_assert_true (ret); - } - else if (response == 1) - { - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - g_assert_false (ret); - } - else if (response == 2) - { - g_assert_error (error, domain, code); - g_assert_false (ret); - } - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_open_uri_http (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - unset_openuri_permissions ("x-scheme-handler/http"); - enable_paranoid_mode ("x-scheme-handler/http"); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "appchooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_uri (portal, NULL, "http://www.flatpak.org", 0, NULL, open_uri_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_uri_http2 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GAppInfo) app = NULL; - g_autofree char *app_id = NULL; - - /* get furrfix.desktop as an app */ - app = g_app_info_get_default_for_type ("x-scheme-handler/xdg-desktop-portal-test", FALSE); - g_assert_nonnull (app); - - app_id = g_strndup (g_app_info_get_id (app), strlen (g_app_info_get_id (app)) - strlen (".desktop")); - - unset_openuri_permissions ("text/plain"); - set_openuri_permissions ("x-scheme-handler/http", app_id, 3, 3); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_boolean (keyfile, "backend", "expect-no-call", 1); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "appchooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_uri (portal, NULL, "http://www.flatpak.org", 0, NULL, open_uri_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_uri_file (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - - unset_openuri_permissions ("text/plain"); - enable_paranoid_mode ("text/plain"); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "appchooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - g_free (path); - path = g_build_filename (outdir, "test.txt", NULL); - g_file_set_contents (path, "text", -1, &error); - g_assert_no_error (error); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_open_uri (portal, NULL, uri, 0, NULL, open_uri_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_uri_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - - unset_openuri_permissions ("text/plain"); - enable_paranoid_mode ("text/plain"); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "appchooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - g_free (path); - path = g_build_filename (outdir, "test.txt", NULL); - g_file_set_contents (path, "text", -1, &error); - g_assert_no_error (error); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_open_uri (portal, NULL, uri, 0, NULL, open_uri_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_uri_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - - unset_openuri_permissions ("text/plain"); - enable_paranoid_mode ("text/plain"); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "appchooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - g_free (path); - path = g_build_filename (outdir, "test.txt", NULL); - g_file_set_contents (path, "text", -1, &error); - g_assert_no_error (error); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_open_uri (portal, NULL, uri, 0, NULL, open_uri_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -static gboolean -cancel_call (gpointer data) -{ - GCancellable *cancellable = data; - - g_debug ("cancel call"); - g_cancellable_cancel (cancellable); - - return G_SOURCE_REMOVE; -} - -void -test_open_uri_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - GCancellable *cancellable; - - unset_openuri_permissions ("text/plain"); - enable_paranoid_mode ("text/plain"); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "appchooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - g_free (path); - path = g_build_filename (outdir, "test.txt", NULL); - g_file_set_contents (path, "text", -1, &error); - g_assert_no_error (error); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_open_uri (portal, NULL, uri, 0, cancellable, open_uri_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_open_uri_lockdown (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-application-handlers", - g_variant_new_boolean (TRUE), - &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED); - - path = g_build_filename (outdir, "appchooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_open_uri (portal, NULL, "http://www.flatpak.org", 0, NULL, open_uri_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-application-handlers", - g_variant_new_boolean (FALSE), - &error); - g_assert_no_error (error); -} - -static void -open_dir_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - GKeyFile *keyfile = data; - gboolean ret; - int response; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - - ret = xdp_portal_open_directory_finish (portal, result, &error); - if (response == 0) - { - g_assert_no_error (error); - g_assert_true (ret); - } - else if (response == 1) - { - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - g_assert_false (ret); - } - else if (response == 2) - { - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); - g_assert_false (ret); - } - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_open_directory (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - g_autoptr(GAppInfo) app = NULL; - - keyfile = g_key_file_new (); - - app = g_app_info_get_default_for_type ("inode/directory", FALSE); - - if (app == NULL) - { - g_test_skip ("No default handler for inode/directory set"); - return; - } - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", app != NULL ? 0 : 2); - - path = g_build_filename (outdir, "appchooser", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - g_free (path); - path = g_build_filename (outdir, "test.txt", NULL); - g_file_set_contents (path, "text", -1, &error); - g_assert_no_error (error); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_open_directory (portal, NULL, uri, 0, NULL, open_dir_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} diff --git a/tests/openuri.h b/tests/openuri.h deleted file mode 100644 index f5dcf87..0000000 --- a/tests/openuri.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -void test_open_uri_http (void); -void test_open_uri_http2 (void); -void test_open_uri_file (void); -void test_open_uri_delay (void); -void test_open_uri_close (void); -void test_open_uri_cancel (void); -void test_open_uri_lockdown (void); -void test_open_directory (void); diff --git a/tests/portals/limited/limited-portals.conf b/tests/portals/limited/limited-portals.conf deleted file mode 100644 index 57b1642..0000000 --- a/tests/portals/limited/limited-portals.conf +++ /dev/null @@ -1,6 +0,0 @@ -[preferred] -default=none -org.freedesktop.impl.portal.Account=limited -org.freedesktop.impl.portal.FileChooser=limited -org.freedesktop.impl.portal.Lockdown=limited -org.freedesktop.impl.portal.Settings=limited diff --git a/tests/portals/limited/limited.portal.in b/tests/portals/limited/limited.portal.in deleted file mode 100644 index 9e652aa..0000000 --- a/tests/portals/limited/limited.portal.in +++ /dev/null @@ -1,3 +0,0 @@ -[portal] -DBusName=org.freedesktop.impl.portal.Limited -Interfaces=@PORTALS@ diff --git a/tests/portals/limited/meson.build b/tests/portals/limited/meson.build deleted file mode 100644 index bc4897d..0000000 --- a/tests/portals/limited/meson.build +++ /dev/null @@ -1,13 +0,0 @@ -limited_portal = configure_file(input: 'limited.portal.in', - output: 'limited.portal', - configuration: test_portal_conf, - install: enable_installed_tests, - install_dir: installed_tests_dir / 'portals/limited', -) - -configure_file(input: 'limited-portals.conf', - output: '@PLAINNAME@', - copy: true, - install: enable_installed_tests, - install_dir: installed_tests_dir / 'portals/limited', -) diff --git a/tests/portals/meson.build b/tests/portals/meson.build deleted file mode 100644 index 96b9326..0000000 --- a/tests/portals/meson.build +++ /dev/null @@ -1,25 +0,0 @@ -test_portals = [ - 'org.freedesktop.impl.portal.Access', - 'org.freedesktop.impl.portal.Account', - 'org.freedesktop.impl.portal.AppChooser', - 'org.freedesktop.impl.portal.Background', - 'org.freedesktop.impl.portal.Clipboard', - 'org.freedesktop.impl.portal.Email', - 'org.freedesktop.impl.portal.FileChooser', - 'org.freedesktop.impl.portal.GlobalShortcuts', - 'org.freedesktop.impl.portal.Inhibit', - 'org.freedesktop.impl.portal.InputCapture', - 'org.freedesktop.impl.portal.Lockdown', - 'org.freedesktop.impl.portal.Notification', - 'org.freedesktop.impl.portal.Print', - 'org.freedesktop.impl.portal.RemoteDesktop', - 'org.freedesktop.impl.portal.Screenshot', - 'org.freedesktop.impl.portal.Settings', - 'org.freedesktop.impl.portal.Wallpaper', -] - -test_portal_conf = configuration_data() -test_portal_conf.set('PORTALS', ';'.join(test_portals)) - -subdir('test') -subdir('limited') diff --git a/tests/portals/test/meson.build b/tests/portals/test/meson.build deleted file mode 100644 index 5dd04b5..0000000 --- a/tests/portals/test/meson.build +++ /dev/null @@ -1,13 +0,0 @@ -test_portal = configure_file(input: 'test.portal.in', - output: 'test.portal', - configuration: test_portal_conf, - install: enable_installed_tests, - install_dir: installed_tests_dir / 'portals/test', -) - -configure_file(input: 'test-portals.conf', - output: '@PLAINNAME@', - copy: true, - install: enable_installed_tests, - install_dir: installed_tests_dir / 'portals/test', -) diff --git a/tests/portals/test/test-portals.conf b/tests/portals/test/test-portals.conf deleted file mode 100644 index d2b8e4d..0000000 --- a/tests/portals/test/test-portals.conf +++ /dev/null @@ -1,2 +0,0 @@ -[preferred] -default=test; diff --git a/tests/portals/test/test.portal.in b/tests/portals/test/test.portal.in deleted file mode 100644 index ba3c8c4..0000000 --- a/tests/portals/test/test.portal.in +++ /dev/null @@ -1,3 +0,0 @@ -[portal] -DBusName=org.freedesktop.impl.portal.Test -Interfaces=@PORTALS@ diff --git a/tests/print.c b/tests/print.c deleted file mode 100644 index 2a840c1..0000000 --- a/tests/print.c +++ /dev/null @@ -1,503 +0,0 @@ -#include - -#include "print.h" - -#include -#include "xdp-utils.h" -#include "xdp-impl-dbus.h" - -#include "utils.h" - -extern XdpDbusImplLockdown *lockdown; - -extern char outdir[]; - -static int got_info; - -static void -prepare_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - g_autoptr(GVariant) ret = NULL; - GKeyFile *keyfile = data; - int response; - int domain; - int code; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - domain = g_key_file_get_integer (keyfile, "result", "error_domain", NULL); - code = g_key_file_get_integer (keyfile, "result", "error_code", NULL); - - ret = xdp_portal_prepare_print_finish (portal, result, &error); - if (response == 0) - { - int expected, token; - - g_assert_no_error (error); - - expected = g_key_file_get_integer (keyfile, "result", "token", NULL); - g_variant_lookup (ret, "token", "u", &token); - - g_assert_cmpint (expected, ==, token); - } - else if (response == 1) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - else if (response == 2) - g_assert_error (error, domain, code); - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_prepare_print_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, NULL, prepare_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_prepare_print_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, NULL, prepare_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_prepare_print_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, NULL, prepare_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -static gboolean -cancel_call (gpointer data) -{ - GCancellable *cancellable = data; - - g_debug ("cancel call"); - g_cancellable_cancel (cancellable); - - return G_SOURCE_REMOVE; -} - -void -test_prepare_print_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GCancellable) cancellable = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, cancellable, prepare_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_prepare_print_lockdown (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-printing", - g_variant_new_boolean (TRUE), - &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, NULL, prepare_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-printing", - g_variant_new_boolean (FALSE), - &error); - g_assert_no_error (error); -} - -void -test_prepare_print_results (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_integer (keyfile, "result", "token", 123); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, NULL, prepare_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_prepare_print_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, NULL, prepare_cb, keyfile); - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, NULL, prepare_cb, keyfile); - xdp_portal_prepare_print (portal, NULL, "test", NULL, NULL, 0, NULL, prepare_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} - -/* test of Print below */ - -static void -print_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - GKeyFile *keyfile = data; - int response; - int domain; - int code; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - domain = g_key_file_get_integer (keyfile, "result", "error_domain", NULL); - code = g_key_file_get_integer (keyfile, "result", "error_code", NULL); - - xdp_portal_print_file_finish (portal, result, &error); - if (response == 0) - { - g_assert_no_error (error); - } - else if (response == 1) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - else if (response == 2) - g_assert_error (error, domain, code); - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_print_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_print_file (portal, NULL, "test", 0, path, 0, NULL, print_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_print_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_print_file (portal, NULL, "test", 0, path, 0, NULL, print_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_print_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_print_file (portal, NULL, "test", 0, path, 0, NULL, print_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_print_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GCancellable) cancellable = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_print_file (portal, NULL, "test", 0, path, 0, cancellable, print_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_print_lockdown (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-printing", - g_variant_new_boolean (TRUE), - &error); - g_assert_no_error (error); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_integer (keyfile, "result", "error_domain", XDG_DESKTOP_PORTAL_ERROR); - g_key_file_set_integer (keyfile, "result", "error_code", XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_print_file (portal, NULL, "test", 0, path, 0, NULL, print_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); - - tests_set_property_sync (G_DBUS_PROXY (lockdown), - "org.freedesktop.impl.portal.Lockdown", - "disable-printing", - g_variant_new_boolean (FALSE), - &error); - g_assert_no_error (error); -} - -void -test_print_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "print", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_print_file (portal, NULL, "test", 0, path, 0, NULL, print_cb, keyfile); - xdp_portal_print_file (portal, NULL, "test", 0, path, 0, NULL, print_cb, keyfile); - xdp_portal_print_file (portal, NULL, "test", 0, path, 0, NULL, print_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} - diff --git a/tests/print.h b/tests/print.h deleted file mode 100644 index 07ad21f..0000000 --- a/tests/print.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -void test_prepare_print_basic (void); -void test_prepare_print_delay (void); -void test_prepare_print_cancel (void); -void test_prepare_print_close (void); -void test_prepare_print_lockdown (void); -void test_prepare_print_results (void); -void test_prepare_print_parallel (void); - -void test_print_basic (void); -void test_print_delay (void); -void test_print_cancel (void); -void test_print_close (void); -void test_print_lockdown (void); -void test_print_parallel (void); diff --git a/tests/run-test.sh b/tests/run-test.sh new file mode 100755 index 0000000..81daa62 --- /dev/null +++ b/tests/run-test.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# +# - Runs pytest with the required environment to run tests on an x-d-p build +# - By default, the tests run on the first x-d-p build directory that is found +# inside the source tree +# - The BUILDDIR environment variable can be set to a specific x-d-p build +# directory +# - All arguments are passed along to pytest +# - Check tests/README.md for useful environment variables +# +# Examples: +# +# ./run-test.sh ./test_camera.py -k test_version -v -s +# +# BUILDDIR=../_build ./run-test.sh ./test_usb.py +# + +set -euo pipefail + +function fail() +{ + sed -n '/^#$/,/^$/p' "${BASH_SOURCE[0]}" + echo "$1" + exit 1 +} + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +PYTEST=$(command -v "pytest-3" || command -v "pytest") || fail "pytest is missing" + +BUILDDIR=${BUILDDIR:-$(find "${SCRIPT_DIR}/.." -maxdepth 2 -name "build.ninja" -printf "%h\n" -quit)} + +[ ! -f "${BUILDDIR}/build.ninja" ] && fail "Path '${BUILDDIR}' does not appear to be a build dir" + +echo "Running tests on build dir: $(readlink -f "${BUILDDIR}")" +echo "" + +export XDP_VALIDATE_SOUND="$BUILDDIR/src/xdg-desktop-portal-validate-sound" +export XDP_VALIDATE_ICON="$BUILDDIR/src/xdg-desktop-portal-validate-icon" +export XDG_DESKTOP_PORTAL_PATH="$BUILDDIR/src/xdg-desktop-portal" +export XDG_DOCUMENT_PORTAL_PATH="$BUILDDIR/document-portal/xdg-document-portal" +export XDG_PERMISSION_STORE_PATH="$BUILDDIR/document-portal/xdg-permission-store" + +exec "$PYTEST" "$@" diff --git a/tests/screenshot.c b/tests/screenshot.c deleted file mode 100644 index 8c4a933..0000000 --- a/tests/screenshot.c +++ /dev/null @@ -1,503 +0,0 @@ -#include - -#include "screenshot.h" - -#include -#include "xdp-impl-dbus.h" - -extern char outdir[]; - -static int got_info; - -extern XdpDbusImplPermissionStore *permission_store; - -static void -set_screenshot_permissions (const char *permission) -{ - const char *permissions[2] = { NULL, NULL }; - g_autoptr(GError) error = NULL; - - permissions[0] = permission; - xdp_dbus_impl_permission_store_call_set_permission_sync (permission_store, - "screenshot", - TRUE, - "screenshot", - "", - permissions, - NULL, - &error); - g_assert_no_error (error); -} - -static void -reset_screenshot_permissions (void) -{ - set_screenshot_permissions (NULL); -} - -static void -screenshot_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - GKeyFile *keyfile = data; - int response; - g_autofree char *ret = NULL; - g_autofree char *uri = NULL; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - uri = g_key_file_get_string (keyfile, "result", "uri", NULL); - - ret = xdp_portal_take_screenshot_finish (portal, result, &error); - - if (response == 0) - { - g_assert_no_error (error); - g_assert_cmpstr (ret, ==, uri); - } - else if (response == 1) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - else if (response == 2) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_screenshot_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "result", "uri", "file://test/image"); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_take_screenshot (portal, NULL, 0, NULL, screenshot_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* test that everything works as expected when the - * backend takes some time to send its response, as - * is to be expected from a real backend that presents - * dialogs to the user. - */ -void -test_screenshot_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_screenshot_permissions (); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_string (keyfile, "result", "uri", "file://test/image"); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_take_screenshot (portal, NULL, 0, NULL, screenshot_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* Test that user cancellation works as expected. - * We simulate that the user cancels a hypothetical dialog, - * by telling the backend to return 1 as response code. - * And we check that we get the expected G_IO_ERROR_CANCELLED. - */ -void -test_screenshot_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_screenshot_permissions (); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_take_screenshot (portal, NULL, 0, NULL, screenshot_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -static gboolean -cancel_call (gpointer data) -{ - GCancellable *cancellable = data; - - g_debug ("cancel call"); - g_cancellable_cancel (cancellable); - - return G_SOURCE_REMOVE; -} - -/* Test that app-side cancellation works as expected. - * We cancel the cancellable while while the hypothetical - * dialog is up, and tell the backend that it should - * expect a Close call. We rely on the backend to - * verify that that call actually happened. - */ -void -test_screenshot_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GCancellable) cancellable = NULL; - - reset_screenshot_permissions (); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_take_screenshot (portal, NULL, 0, cancellable, screenshot_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_screenshot_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - reset_screenshot_permissions (); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_string (keyfile, "result", "uri", "file://test/image"); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_take_screenshot (portal, NULL, 0, NULL, screenshot_cb, keyfile); - xdp_portal_take_screenshot (portal, NULL, 0, NULL, screenshot_cb, keyfile); - xdp_portal_take_screenshot (portal, NULL, 0, NULL, screenshot_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} - -/* Tests for PickColor below */ - -static void -pick_color_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - GKeyFile *keyfile = data; - int response; - g_autoptr(GVariant) ret = NULL; - double red, green, blue; - g_autoptr(GVariant) expected = NULL; - - red = g_key_file_get_double (keyfile, "result", "red", NULL); - green = g_key_file_get_double (keyfile, "result", "green", NULL); - blue = g_key_file_get_double (keyfile, "result", "blue", NULL); - expected = g_variant_new ("(ddd)", red, green, blue); - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - ret = xdp_portal_pick_color_finish (portal, result, &error); - - if (response == 0) - { - g_assert_no_error (error); - g_assert_true (g_variant_equal (ret, expected)); - } - else if (response == 1) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - else if (response == 2) - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -void -test_color_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_double (keyfile, "result", "red", 0.3); - g_key_file_set_double (keyfile, "result", "green", 0.5); - g_key_file_set_double (keyfile, "result", "blue", 0.7); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_pick_color (portal, NULL, NULL, pick_color_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* test that everything works as expected when the - * backend takes some time to send its response, as - * is to be expected from a real backend that presents - * dialogs to the user. - */ -void -test_color_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_double (keyfile, "result", "red", 0.2); - g_key_file_set_double (keyfile, "result", "green", 0.3); - g_key_file_set_double (keyfile, "result", "blue", 0.4); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_pick_color (portal, NULL, NULL, pick_color_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* Test that user cancellation works as expected. - * We simulate that the user cancels a hypothetical dialog, - * by telling the backend to return 1 as response code. - * And we check that we get the expected G_IO_ERROR_CANCELLED. - */ -void -test_color_cancel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_pick_color (portal, NULL, NULL, pick_color_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -/* Test that app-side cancellation works as expected. - * We cancel the cancellable while while the hypothetical - * dialog is up, and tell the backend that it should - * expect a Close call. We rely on the backend to - * verify that that call actually happened. - */ -void -test_color_close (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autoptr(GCancellable) cancellable = NULL; - - keyfile = g_key_file_new (); - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_boolean (keyfile, "backend", "expect-close", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - cancellable = g_cancellable_new (); - - got_info = 0; - xdp_portal_pick_color (portal, NULL, cancellable, pick_color_cb, keyfile); - - g_timeout_add (100, cancel_call, cancellable); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_color_parallel (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - - set_screenshot_permissions ("no"); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - g_key_file_set_double (keyfile, "result", "red", 0.3); - g_key_file_set_double (keyfile, "result", "green", 0.5); - g_key_file_set_double (keyfile, "result", "blue", 0.7); - - path = g_build_filename (outdir, "screenshot", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - got_info = 0; - xdp_portal_pick_color (portal, NULL, NULL, pick_color_cb, keyfile); - xdp_portal_pick_color (portal, NULL, NULL, pick_color_cb, keyfile); - xdp_portal_pick_color (portal, NULL, NULL, pick_color_cb, keyfile); - - while (got_info < 3) - g_main_context_iteration (NULL, TRUE); -} diff --git a/tests/screenshot.h b/tests/screenshot.h deleted file mode 100644 index 8d03c1a..0000000 --- a/tests/screenshot.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -void test_screenshot_basic (void); -void test_screenshot_delay (void); -void test_screenshot_cancel (void); -void test_screenshot_close (void); -void test_screenshot_parallel (void); - -void test_color_basic (void); -void test_color_delay (void); -void test_color_cancel (void); -void test_color_close (void); -void test_color_parallel (void); diff --git a/tests/services/meson.build b/tests/services/meson.build deleted file mode 100644 index 7b0dc8d..0000000 --- a/tests/services/meson.build +++ /dev/null @@ -1,11 +0,0 @@ -conf_service = configuration_data() -conf_service.set('libexecdir', meson.project_build_root() / 'document-portal') - -configure_file(input: doc_portal_service_file_sources, - output: '@BASENAME@', - configuration: conf_service, - ) -configure_file(input: permission_portal_service_file_sources, - output: '@BASENAME@', - configuration: conf_service, - ) diff --git a/tests/session.conf.in b/tests/session.conf.in deleted file mode 100644 index 307f84a..0000000 --- a/tests/session.conf.in +++ /dev/null @@ -1,56 +0,0 @@ - - - - session - - - - - unix:tmpdir=/tmp - - @testdir@/services - - - - - - - - - - - - - - contexts/dbus_contexts - - - - - 1000000000 - 250000000 - 1000000000 - 250000000 - 1000000000 - 4096 - 120000 - 240000 - 100000 - 10000 - 100000 - 10000 - 50000 - 50000 - 50000 - - diff --git a/tests/share/applications/furrfix.desktop b/tests/share/applications/furrfix.desktop deleted file mode 100644 index f35caf0..0000000 --- a/tests/share/applications/furrfix.desktop +++ /dev/null @@ -1,13 +0,0 @@ -[Desktop Entry] -Version=1.0 -Name=Furrfix -GenericName=Not a Web Browser -Comment=Don't Browse the Web -Exec=true %u -Icon=furrfix -Terminal=false -Type=Application -MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/xdg-desktop-portal-test; -StartupNotify=true -Categories=Network;WebBrowser; -Keywords=web;browser;internet; diff --git a/tests/share/applications/meson.build b/tests/share/applications/meson.build deleted file mode 100644 index d56b633..0000000 --- a/tests/share/applications/meson.build +++ /dev/null @@ -1,2 +0,0 @@ -configure_file(input: 'furrfix.desktop', output: '@PLAINNAME@', copy: true) -configure_file(input: 'mimeinfo.cache', output: '@PLAINNAME@', copy: true) diff --git a/tests/share/applications/mimeinfo.cache b/tests/share/applications/mimeinfo.cache deleted file mode 100644 index 7881f39..0000000 --- a/tests/share/applications/mimeinfo.cache +++ /dev/null @@ -1,9 +0,0 @@ -[MIME Cache] -application/vnd.mozilla.xul+xml=furrfix.desktop; -application/xhtml+xml=furrfix.desktop; -text/html=furrfix.desktop; -text/mml=furrfix.desktop; -text/xml=furrfix.desktop; -x-scheme-handler/http=furrfix.desktop; -x-scheme-handler/https=furrfix.desktop; -x-scheme-handler/xdg-desktop-portal-test=furrfix.desktop; diff --git a/tests/share/meson.build b/tests/share/meson.build deleted file mode 100644 index 48c4428..0000000 --- a/tests/share/meson.build +++ /dev/null @@ -1 +0,0 @@ -subdir('applications') diff --git a/tests/template.test.in b/tests/template.test.in index 01c1e19..dbdbb23 100644 --- a/tests/template.test.in +++ b/tests/template.test.in @@ -1,4 +1,3 @@ [Test] Type=session -Exec=@installed_testdir@/@exec@ --tap -Output=TAP +Exec=env LSAN_OPTIONS=exitcode=0 LD_LIBRARY_PATH=@libdir@:$LD_LIBRARY_PATH @env@ @exec@ diff --git a/tests/templates/__init__.py b/tests/templates/__init__.py index 0ce724b..c7f2f9e 100644 --- a/tests/templates/__init__.py +++ b/tests/templates/__init__.py @@ -3,17 +3,18 @@ # This file is formatted with Python Black from typing import Callable, Dict, Optional, NamedTuple +from gi.repository import GLib import dbus import dbusmock import logging -def init_template_logger(name: str): +def init_logger(name: str) -> logging.Logger: """ Common logging setup for the impl.portal templates. Use as: - >>> from tests.templates import init_template_logger - >>> logger = init_template_logger(__name__) + >>> from tests.templates import init_logger + >>> logger = init_logger(__name__) >>> logger.debug("foo") """ @@ -25,7 +26,7 @@ def init_template_logger(name: str): return logger -logger = init_template_logger("request") +logger = init_logger("utils") class Response(NamedTuple): @@ -35,62 +36,116 @@ class Response(NamedTuple): class ImplRequest: """ - Implementation of a org.freedesktop.impl.portal.Request object. Typically - this object needs to be merely exported: + Implementation of an ``org.freedesktop.impl.portal.Request`` object exposed + on the object path ``handle``. - >>> r = ImplRequest(mock, "org.freedesktop.impl.portal.Test", handle) - >>> r.export() - - Where the test or the backend implementation relies on the Closed() method - of the ImplRequest, provide a callback to be invoked. - - >>> r.export(close_callback=my_callback) - - Note that the latter only works if the test invokes methods - asynchronously. - - .. attribute:: closed - - Set to True if the Close() method on the Request was invoked + The dbus method implementations need to be invoked asynchronously and the + async callbacks must be passed in ``cb_success`` and ``cb_error``. + The request either waits until it is closed by x-d-p (``wait_for_close``) or + responds to the request (``respond``). """ - def __init__(self, mock: "dbusmock.DBusMockObject", busname: str, handle: str): + def __init__( + self, + mock: dbusmock.DBusMockObject, + busname: str, + handle: str, + logger: logging.Logger, + cb_success: Callable, + cb_error: Callable, + ): self.mock = mock - self.handle = handle - self.closed = False - self._close_callback: Optional[Callable] = None - bus = mock.connection proxy = bus.get_object(busname, handle) - mock_interface = dbus.Interface(proxy, dbusmock.MOCK_IFACE) + self.mock_interface = dbus.Interface(proxy, dbusmock.MOCK_IFACE) + self.handle = handle + self.logger = logger + self.cb_success = cb_success + self.cb_error = cb_error + + def respond( + self, + response: Callable | Response, + delay: int = 0, + done_cb: Callable | None = None, + ) -> None: + def reply(): + nonlocal response + res = None + logger.debug(f"Request {self.handle}: trying to reply") + if callable(response): + try: + res = response() + except Exception as e: + logger.critical( + f"Request {self.handle}: failed getting response: {e}" + ) + self.cb_error(e) + self._unexport() + return + else: + res = response + + assert res + logger.debug(f"Request {self.handle}: replying {res}") + + self.cb_success(res.response, res.results) + self._unexport() + + if done_cb: + done_cb() + + self._export() + + if delay > 0: + logger.debug(f"Request {self.handle}: scheduling delay of {delay}ms") + GLib.timeout_add(delay, reply) + else: + reply() + + def wait_for_close( + self, + close_callback: Callable | None = None, + ) -> None: + def closed(): + logger.debug(f"Request {self.handle}: closed") + + self.mock.EmitSignal( + "org.freedesktop.impl.portal.Mock", + "RequestClosed", + "s", + (self.handle,), + ) + + if close_callback: + try: + close_callback() + except Exception as e: + logger.critical( + f"Request {self.handle}: failed running close callback: {e}" + ) + self.cb_error(e) + self._unexport() + return + + response = Response(2, {}) + self.cb_success(response.response, response.results) + self._unexport() - # Register for the Close() call on the impl.Request. If it gets - # called, use the side-channel RequestClosed signal so we can notify - # the test that the impl.Request was actually closed by the - # xdg-desktop-portal def cb_methodcall(name, args): if name == "Close": - self.closed = True - logger.debug(f"Close() on {self}") - if self._close_callback: - self._close_callback() - self.mock.EmitSignal( - "org.freedesktop.impl.portal.Test", - "RequestClosed", - "s", - (self.handle,), - ) - self.mock.RemoveObject(self.handle) + closed() - mock_interface.connect_to_signal("MethodCalled", cb_methodcall) + self._export() - def export(self, close_callback: Optional[Callable] = None): - """ - Create the object on the bus. If close_callback is not None, that - callback will be invoked in response to the Close() method called on - this object. - """ + logger.debug(f"Request {self.handle}: waiting for x-d-p to call close") + self.mock_interface.connect_to_signal("MethodCalled", cb_methodcall) + + def _export(self): + # In the future we can pass a class extending + # dbusmock.mockobject.DBusMockObject as mock_class to avoid going + # through the mock MethodCalled signal self.mock.AddObject( path=self.handle, interface="org.freedesktop.impl.portal.Request", @@ -104,8 +159,9 @@ def export(self, close_callback: Optional[Callable] = None): ) ], ) - self._close_callback = close_callback - return self + + def _unexport(self): + self.mock.RemoveObject(self.handle) def __str__(self): return f"ImplRequest {self.handle}" @@ -142,9 +198,11 @@ def __init__( mock: dbusmock.DBusMockObject, busname: str, handle: str, + app_id: str, ): self.mock = mock # the main mock object self.handle = handle + self.app_id = app_id self.closed = False self._close_callback: Optional[Callable] = None @@ -165,7 +223,7 @@ def cb_methodcall(name, args): if self._close_callback: self._close_callback() self.mock.EmitSignal( - "org.freedesktop.impl.portal.Test", + "org.freedesktop.impl.portal.Mock", "SessionClosed", "s", (self.handle,), @@ -203,6 +261,7 @@ def export( # In theory, EmitSignal should work on self.mock_interface but # it doesn't and I can't figure out why. self.mock_object = dbusmock.get_object(self.handle) + self._close_callback = close_callback return self def _unexport(self): diff --git a/tests/templates/access.py b/tests/templates/access.py new file mode 100644 index 0000000..4c198ac --- /dev/null +++ b/tests/templates/access.py @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus.service +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Access" + + +logger = init_logger(__name__) + + +@dataclass +class AccessParameters: + delay: int + response: int + expect_close: bool + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "access_params") + mock.access_params = AccessParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="osssssa{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def AccessDialog( + self, + handle, + app_id, + parent_window, + title, + subtitle, + body, + options, + cb_success, + cb_error, +): + logger.debug( + f"AccessDialog({handle}, {app_id}, {parent_window}, {title}, {subtitle}, {body}, {options})" + ) + params = self.access_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, {}), delay=params.delay) diff --git a/tests/templates/account.py b/tests/templates/account.py new file mode 100644 index 0000000..8db3010 --- /dev/null +++ b/tests/templates/account.py @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus.service +import dbus +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Account" + + +logger = init_logger(__name__) + + +@dataclass +class AccountParameters: + delay: int + response: int + results: dict + expect_close: bool + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "account_params") + mock.account_params = AccountParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + results=parameters.get("results", {}), + expect_close=parameters.get("expect-close", False), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ossa{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def GetUserInformation(self, handle, app_id, window, options, cb_success, cb_error): + logger.debug(f"GetUserInformation({handle}, {app_id}, {window}, {options})") + params = self.account_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, params.results), delay=params.delay) diff --git a/tests/templates/appchooser.py b/tests/templates/appchooser.py new file mode 100644 index 0000000..2a5f056 --- /dev/null +++ b/tests/templates/appchooser.py @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus.service +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.AppChooser" +VERSION = 2 + + +logger = init_logger(__name__) + + +@dataclass +class AppchooserParameters: + delay: int + response: int + expect_close: bool + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "appchooser_params") + mock.appchooser_params = AppchooserParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + ) + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + } + ), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ossasa{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def ChooseApplication( + self, handle, app_id, parent_window, choices, options, cb_success, cb_error +): + logger.debug( + f"ChooseApplication({handle}, {app_id}, {parent_window}, {choices}, {options})" + ) + params = self.appchooser_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, {}), delay=params.delay) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="oas", + out_signature="", +) +def UpdateChoices(self, handle, choices): + logger.debug(f"UpdateChoices({handle}, {choices})") diff --git a/tests/templates/background.py b/tests/templates/background.py new file mode 100644 index 0000000..f17bb1e --- /dev/null +++ b/tests/templates/background.py @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import init_logger + +import dbus.service +import dbus +from gi.repository import GLib +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Background" +VERSION = 1 + + +logger = init_logger(__name__) + + +@dataclass +class BackgroundParameters: + delay: int + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "background_params") + mock.background_params = BackgroundParameters( + delay=parameters.get("delay", 200), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="", + out_signature="a{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def GetAppState(self, cb_success, cb_error): + logger.debug("GetAppState()") + params = self.background_params + + # FIXME: implement? + def reply(): + cb_success({}) + + logger.debug(f"scheduling delay of {params.delay}") + GLib.timeout_add(params.delay, reply) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="oss", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def NotifyBackground(self, handle, app_id, name, cb_success, cb_error): + logger.debug(f"NotifyBackground({handle}, {app_id}, {name})") + params = self.background_params + + logger.debug(f"scheduling delay of {params.delay}") + GLib.timeout_add(params.delay, cb_success) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="sbasu", + out_signature="b", +) +def EnableAutostart(self, app_id, enable, commandline, flags): + raise dbus.exceptions.DBusException("EnableAutostart is deprecated") diff --git a/tests/templates/clipboard.py b/tests/templates/clipboard.py index 8b55fc8..170cf8e 100644 --- a/tests/templates/clipboard.py +++ b/tests/templates/clipboard.py @@ -1,13 +1,16 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import init_logger -from tests.templates import init_template_logger import dbus.service import dbus import tempfile - from gi.repository import GLib +from dataclasses import dataclass + BUS_NAME = "org.freedesktop.impl.portal.Test" MAIN_OBJ = "/org/freedesktop/portal/desktop" @@ -15,15 +18,26 @@ MAIN_IFACE = "org.freedesktop.impl.portal.Clipboard" VERSION = 1 -logger = init_template_logger(__name__) + +logger = init_logger(__name__) -def load(mock, parameters=None): +@dataclass +class ClipboardParameters: + delay: int + response: int + expect_close: bool + + +def load(mock, parameters={}): logger.debug(f"Loading parameters: {parameters}") - mock.delay: int = parameters.get("delay", 200) - mock.response: int = parameters.get("response", 0) - mock.expect_close: bool = parameters.get("expect-close", False) + assert not hasattr(mock, "clipboard_params") + mock.clipboard_params = ClipboardParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + ) mock.AddProperties( MAIN_IFACE, @@ -44,12 +58,13 @@ def load(mock, parameters=None): def RequestClipboard(self, session_handle, options, cb_success, cb_error): try: logger.debug(f"RequestClipboard({session_handle}, {options})") + params = self.clipboard_params - if self.expect_close: + if params.expect_close: cb_success() else: - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, cb_success) + logger.debug(f"scheduling delay of {params.delay}") + GLib.timeout_add(params.delay, cb_success) except Exception as e: logger.critical(e) cb_error(e) @@ -64,12 +79,13 @@ def RequestClipboard(self, session_handle, options, cb_success, cb_error): def SetSelection(self, session_handle, options, cb_success, cb_error): try: logger.debug(f"SetSelection({session_handle}, {options})") + params = self.clipboard_params - if self.expect_close: + if params.expect_close: cb_success() else: - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, cb_success) + logger.debug(f"scheduling delay of {params.delay}") + GLib.timeout_add(params.delay, cb_success) except Exception as e: logger.critical(e) cb_error(e) @@ -84,19 +100,20 @@ def SetSelection(self, session_handle, options, cb_success, cb_error): def SelectionWrite(self, session_handle, serial, cb_success, cb_error): try: logger.debug(f"SelectionWrite({session_handle}, {serial})") + params = self.clipboard_params temp_file = tempfile.TemporaryFile() fd = dbus.types.UnixFd(temp_file.fileno()) - if self.expect_close: + if params.expect_close: cb_success(fd) else: def reply(): cb_success(fd) - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, reply) + logger.debug(f"scheduling delay of {params.delay}") + GLib.timeout_add(params.delay, reply) except Exception as e: logger.critical(e) cb_error(e) @@ -111,12 +128,13 @@ def reply(): def SelectionWriteDone(self, session_handle, serial, success, cb_success, cb_error): try: logger.debug(f"SelectionWriteDone({session_handle}, {serial}, {success})") + params = self.clipboard_params - if self.expect_close: + if params.expect_close: cb_success() else: - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, cb_success) + logger.debug(f"scheduling delay of {params.delay}") + GLib.timeout_add(params.delay, cb_success) except Exception as e: logger.critical(e) cb_error(e) @@ -131,19 +149,20 @@ def SelectionWriteDone(self, session_handle, serial, success, cb_success, cb_err def SelectionRead(self, session_handle, mime_type, cb_success, cb_error): try: logger.debug(f"SelectionRead({session_handle}, {mime_type})") + params = self.clipboard_params temp_file = tempfile.TemporaryFile() fd = dbus.types.UnixFd(temp_file.fileno()) - if self.expect_close: + if params.expect_close: cb_success(fd) else: def reply(): cb_success(fd) - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, reply) + logger.debug(f"scheduling delay of {params.delay}") + GLib.timeout_add(params.delay, reply) except Exception as e: logger.critical(e) cb_error(e) diff --git a/tests/templates/dynamiclauncher.py b/tests/templates/dynamiclauncher.py new file mode 100644 index 0000000..96ac752 --- /dev/null +++ b/tests/templates/dynamiclauncher.py @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus.service +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.DynamicLauncher" +VERSION = 1 + + +logger = init_logger(__name__) + + +@dataclass +class DynamiclauncherParameters: + delay: int + response: int + expect_close: bool + launcher_name: str + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "dynamiclauncher_params") + mock.dynamiclauncher_params = DynamiclauncherParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + launcher_name=parameters.get("launcher-name", None), + ) + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + } + ), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="osssva{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def PrepareInstall( + self, handle, app_id, parent_window, name, icon_v, options, cb_success, cb_error +): + logger.debug( + f"PrepareInstall({handle}, {app_id}, {parent_window}, {name}, {icon_v}, {options})" + ) + params = self.dynamiclauncher_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + response = Response( + params.response, + { + "name": params.launcher_name if params.launcher_name else name, + "icon": dbus.Struct(list(icon_v), signature="sv", variant_level=2), + }, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(response, delay=params.delay) diff --git a/tests/templates/email.py b/tests/templates/email.py index 466f44e..5d56b13 100644 --- a/tests/templates/email.py +++ b/tests/templates/email.py @@ -1,11 +1,12 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # This file is formatted with Python Black +# mypy: disable-error-code="misc" -from tests.templates import Response, init_template_logger, ImplRequest -import dbus.service +from tests.templates import Response, init_logger, ImplRequest -from gi.repository import GLib +import dbus.service +from dataclasses import dataclass BUS_NAME = "org.freedesktop.impl.portal.Test" @@ -15,15 +16,26 @@ VERSION = 3 -logger = init_template_logger(__name__) +logger = init_logger(__name__) + + +@dataclass +class EmailParameters: + delay: int + response: int + expect_close: bool -def load(mock, parameters=None): +def load(mock, parameters={}): logger.debug(f"Loading parameters: {parameters}") - mock.delay: int = parameters.get("delay", 200) - mock.response: int = parameters.get("response", 0) - mock.expect_close: bool = parameters.get("expect-close", False) + assert not hasattr(mock, "email_params") + mock.email_params = EmailParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + ) + mock.AddProperties( MAIN_IFACE, dbus.Dictionary( @@ -41,30 +53,19 @@ def load(mock, parameters=None): async_callbacks=("cb_success", "cb_error"), ) def ComposeEmail(self, handle, app_id, parent_window, options, cb_success, cb_error): - try: - logger.debug(f"ComposeEmail({handle}, {app_id}, {parent_window}, {options})") - - response = Response(self.response, {}) - - request = ImplRequest(self, BUS_NAME, handle) - - if self.expect_close: - - def closed_callback(): - response = Response(2, {}) - logger.debug(f"ComposeEmail Close() response {response}") - cb_success(response.response, response.results) - - request.export(closed_callback) - else: - request.export() - - def reply(): - logger.debug(f"ComposeEmail with response {response}") - cb_success(response.response, response.results) + logger.debug(f"ComposeEmail({handle}, {app_id}, {parent_window}, {options})") + params = self.email_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, reply) - except Exception as e: - logger.critical(e) - cb_error(e) + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, {}), delay=params.delay) diff --git a/tests/templates/filechooser.py b/tests/templates/filechooser.py new file mode 100644 index 0000000..f9ef6d7 --- /dev/null +++ b/tests/templates/filechooser.py @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus.service +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.FileChooser" +VERSION = 4 + + +logger = init_logger(__name__) + + +@dataclass +class FilechooserParameters: + delay: int + response: int + results: dict + expect_close: bool + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "filechooser_params") + mock.filechooser_params = FilechooserParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + results=parameters.get("results", {}), + expect_close=parameters.get("expect-close", False), + ) + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + } + ), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="osssa{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def OpenFile(self, handle, app_id, parent_window, title, options, cb_success, cb_error): + logger.debug(f"OpenFile({handle}, {app_id}, {parent_window}, {title}, {options})") + params = self.filechooser_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, params.results), delay=params.delay) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="osssa{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def SaveFile(self, handle, app_id, parent_window, title, options, cb_success, cb_error): + logger.debug(f"SaveFile({handle}, {app_id}, {parent_window}, {title}, {options})") + params = self.filechooser_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, params.results), delay=params.delay) diff --git a/tests/templates/geoclue2.py b/tests/templates/geoclue2.py new file mode 100644 index 0000000..c7988a4 --- /dev/null +++ b/tests/templates/geoclue2.py @@ -0,0 +1,121 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# mypy: disable-error-code="misc" + +from tests.templates import init_logger + +import dbus.service +import dbus + +from dbusmock import mockobject + +BUS_NAME = "org.freedesktop.GeoClue2" +MAIN_OBJ = "/org/freedesktop/GeoClue2/Manager" +MAIN_IFACE = "org.freedesktop.GeoClue2.Manager" +CLIENT_IFACE = "org.freedesktop.GeoClue2.Client" +LOCATION_IFACE = "org.freedesktop.GeoClue2.Location" +MOCK_IFACE = "org.freedesktop.GeoClue2.Mock" +SYSTEM_BUS = True +VERSION = 1 + +logger = init_logger(__name__) + + +def load(mock, parameters={}): + mock.AddMethods( + MAIN_IFACE, + [ + ( + "GetClient", + "", + "o", + 'ret = dbus.ObjectPath("/org/freedesktop/GeoClue2/Client/1")', + ), + ], + ) + mock.AddObject( + "/org/freedesktop/GeoClue2/Client/1", + CLIENT_IFACE, + { + "DesktopId": "", + "DistanceThreshold": 0, + "TimeThreshold": 0, + "RequestedAccuracyLevel": 0, + }, + [ + ("Start", "", "", Start), + ("Stop", "", "", Stop), + ], + ) + mock.client = mockobject.objects["/org/freedesktop/GeoClue2/Client/1"] + mock.client.manager = mock + mock.client.started = False + mock.client.location = 0 + mock.client.props = {"Latitude": 0, "Longitude": 0, "Accuracy": 0} + + mock.client.AddMethod(MOCK_IFACE, "ChangeLocation", "a{sv}", "", ChangeLocation) + + +@dbus.service.method( + CLIENT_IFACE, + in_signature="", + out_signature="", +) +def Start(self): + logger.debug("Start()") + self.started = True + self.ChangeLocation(self.props) + + +@dbus.service.method( + CLIENT_IFACE, + in_signature="", + out_signature="", +) +def Stop(self): + logger.debug("Stop()") + self.started = False + self.RemoveObject(f"/org/freedesktop/GeoClue2/Location/{self.location}") + + +@dbus.service.method( + MOCK_IFACE, + in_signature="a{sv}", + out_signature="", +) +def ChangeLocation(self, props): + logger.debug(f"ChangeLocation({props})") + + self.props = props + + if not self.started: + return + + old_path = "/" + if self.location > 0: + old_path = f"/org/freedesktop/GeoClue2/Location/{self.location}" + self.location = self.location + 1 + new_path = f"/org/freedesktop/GeoClue2/Location/{self.location}" + + self.AddObject( + new_path, + LOCATION_IFACE, + { + "Latitude": props["Latitude"], + "Longitude": props["Longitude"], + "Accuracy": props["Accuracy"], + }, + [], + ) + + if old_path != "/": + self.RemoveObject(old_path) + + self.EmitSignal( + CLIENT_IFACE, + "LocationUpdated", + "oo", + [ + dbus.ObjectPath(old_path), + dbus.ObjectPath(new_path), + ], + ) diff --git a/tests/templates/globalshortcuts.py b/tests/templates/globalshortcuts.py index 876632d..d738492 100644 --- a/tests/templates/globalshortcuts.py +++ b/tests/templates/globalshortcuts.py @@ -1,14 +1,16 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest, ImplSession -from tests.templates import Response, init_template_logger, ImplRequest, ImplSession import dbus import dbus.service import time from dbusmock import MOCK_IFACE - from gi.repository import GLib +from dataclasses import dataclass BUS_NAME = "org.freedesktop.impl.portal.Test" @@ -18,16 +20,28 @@ VERSION = 1 -logger = init_template_logger(__name__) +logger = init_logger(__name__) + +@dataclass +class GlobalshortcutsParameters: + delay: int + response: int + expect_close: bool + force_close: int -def load(mock, parameters): + +def load(mock, parameters={}): logger.debug(f"Loading parameters: {parameters}") - mock.delay: int = parameters.get("delay", 200) - mock.response: int = parameters.get("response", 0) - mock.expect_close: bool = parameters.get("expect-close", False) - mock.force_close: int = parameters.get("force-close", 0) + assert not hasattr(mock, "globalshortcuts_params") + mock.globalshortcuts_params = GlobalshortcutsParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + force_close=parameters.get("force-close", 0), + ) + mock.AddProperties( MAIN_IFACE, dbus.Dictionary( @@ -46,44 +60,30 @@ def load(mock, parameters): async_callbacks=("cb_success", "cb_error"), ) def CreateSession(self, handle, session_handle, app_id, options, cb_success, cb_error): - try: - logger.debug(f"CreateSession({handle}, {session_handle}, {app_id}, {options})") - - session = ImplSession(self, BUS_NAME, session_handle).export() - self.sessions[session_handle] = session - - response = Response(self.response, {"session_handle": session.handle}) - - request = ImplRequest(self, BUS_NAME, handle) - - if self.expect_close: - - def closed_callback(): - response = Response(2, {}) - logger.debug(f"CreateSession Close() response {response}") - cb_success(response.response, response.results) - - request.export(closed_callback) - else: - request.export() - - def reply(): - logger.debug(f"CreateSession with response {response}") - cb_success(response.response, response.results) - - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, reply) - - if self.force_close > 0: - - def force_close(): - session.close() - - GLib.timeout_add(self.force_close, force_close) + logger.debug(f"CreateSession({handle}, {session_handle}, {app_id}, {options})") + params = self.globalshortcuts_params + + session = ImplSession(self, BUS_NAME, session_handle, app_id).export() + self.sessions[session_handle] = session + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) - except Exception as e: - logger.critical(e) - cb_error(e) + if params.expect_close: + request.wait_for_close() + else: + request.respond( + Response(params.response, {"session_handle": session.handle}), + delay=params.delay, + ) + if params.force_close > 0: + GLib.timeout_add(params.force_close, session.close) @dbus.service.method( @@ -102,27 +102,30 @@ def BindShortcuts( cb_success, cb_error, ): - try: - logger.debug( - f"BindShortcuts({handle}, {session_handle}, {shortcuts}, {options})" - ) + logger.debug(f"BindShortcuts({handle}, {session_handle}, {shortcuts}, {options})") + params = self.globalshortcuts_params + + assert session_handle in self.sessions + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) - assert session_handle in self.sessions - response = Response(self.response, {}) - request = ImplRequest(self, BUS_NAME, handle) - request.export() + if params.expect_close: + request.wait_for_close() + else: def reply(): - logger.debug(f"BindShortcuts with response {response}") + logger.debug(f"BindShortcuts with shortcuts {shortcuts}") self.sessions[session_handle].shortcuts = shortcuts - cb_success(response.response, response.results) - - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, reply) + return Response(params.response, {}) - except Exception as e: - logger.critical(e) - cb_error(e) + request.respond(reply, delay=params.delay) @dbus.service.method( diff --git a/tests/templates/inhibit.py b/tests/templates/inhibit.py new file mode 100644 index 0000000..3645118 --- /dev/null +++ b/tests/templates/inhibit.py @@ -0,0 +1,164 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest, ImplSession + +import dbus.service +from gi.repository import GLib +from enum import Enum +from dbusmock import MOCK_IFACE +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Inhibit" +VERSION = 3 + + +logger = init_logger(__name__) + + +class SessionState(Enum): + RUNNING = 1 + QUERY_END = 2 + ENDING = 3 + + +@dataclass +class InhibitParameters: + delay: int + response: int + expect_close: bool + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "inhibit_params") + mock.inhibit_params = InhibitParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + ) + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + } + ), + ) + mock.sessions: dict[str, ImplSession] = {} + mock.session_timers = {} + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ossua{sv}", + out_signature="", + async_callbacks=("cb_success", "cb_error"), +) +def Inhibit(self, handle, app_id, window, flags, options, cb_success, cb_error): + logger.debug(f"Inhibit({handle}, {app_id}, {window}, {flags}, {options})") + params = self.inhibit_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, {}), delay=params.delay) + + +@dbus.service.method( + MOCK_IFACE, + in_signature="s", + out_signature="", +) +def ArmTimer(self, session_handle): + self.EmitSignal( + MAIN_IFACE, + "StateChanged", + "oa{sv}", + [ + session_handle, + { + "screensaver-active": False, + "session-state": SessionState.QUERY_END.value, + }, + ], + ) + + def close_session(): + session = self.sessions[session_handle] + session.close() + self.sessions[session_handle] = None + + if session_handle in self.session_timers: + GLib.source_remove(self.session_timers[session_handle]) + self.session_timers[session_handle] = GLib.timeout_add(700, close_session) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ooss", + out_signature="u", + async_callbacks=("cb_success", "cb_error"), +) +def CreateMonitor(self, handle, session_handle, app_id, window, cb_success, cb_error): + logger.debug(f"CreateMonitor({handle}, {session_handle}, {app_id}, {window})") + params = self.inhibit_params + + session = ImplSession(self, BUS_NAME, session_handle, app_id).export() + self.sessions[session_handle] = session + + # This is irregular: the backend doesn't return the results vardict + def internal_cb_success(response, results): + cb_success(response) + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + internal_cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + + def arm_timer(): + self.ArmTimer(session_handle) + + request.respond( + Response(params.response, {}), delay=params.delay, done_cb=arm_timer + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="o", + out_signature="", +) +def QueryEndResponse(self, session_handle): + try: + logger.debug(f"QueryEndResponse({session_handle})") + + self.ArmTimer(session_handle) + + except Exception as e: + logger.critical(e) diff --git a/tests/templates/inputcapture.py b/tests/templates/inputcapture.py index e9c2ecc..d6ba7b8 100644 --- a/tests/templates/inputcapture.py +++ b/tests/templates/inputcapture.py @@ -1,54 +1,73 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger from collections import namedtuple from itertools import count from gi.repository import GLib - +from dataclasses import dataclass import dbus import dbus.service -import logging import socket + BUS_NAME = "org.freedesktop.impl.portal.Test" MAIN_OBJ = "/org/freedesktop/portal/desktop" SYSTEM_BUS = False MAIN_IFACE = "org.freedesktop.impl.portal.InputCapture" VERSION = 1 -logger = logging.getLogger(f"templates.{__name__}") -logger.setLevel(logging.DEBUG) + +logger = init_logger(__name__) + serials = count() -Response = namedtuple("Response", ["response", "results"]) + Barrier = namedtuple("Barrier", ["id", "position"]) -def load(mock, parameters=None): +@dataclass +class InputcaptureParameters: + delay: int + supported_capabilities: int + capabilities: int + default_zone: list + disable_delay: int + activated_delay: int + deactivated_delay: int + zones_changed_delay: int + + +def load(mock, parameters={}): logger.debug(f"Loading parameters: {parameters}") - # Delay before Request.response - mock.delay: int = parameters.get("delay", 0) - mock.supported_capabilities = parameters.get("supported_capabilities", 0xF) - # The actual ones we reply with in the CreateSession request - mock.capabilities = parameters.get("capabilities", None) + assert not hasattr(mock, "inputcapture_params") + mock.inputcapture_params = InputcaptureParameters( + delay=parameters.get("delay", 0), + supported_capabilities=parameters.get("supported_capabilities", 0xF), + capabilities=parameters.get("capabilities", None), + default_zone=parameters.get("default-zone", [(1920, 1080, 0, 0)]), + disable_delay=parameters.get("disable-delay", 0), + activated_delay=parameters.get("activated-delay", 0), + deactivated_delay=parameters.get("deactivated-delay", 0), + zones_changed_delay=parameters.get("zones-changed-delay", 0), + ) - mock.default_zone = parameters.get("default-zone", [(1920, 1080, 0, 0)]) - mock.current_zones = mock.default_zone + mock.current_zones = mock.inputcapture_params.default_zone mock.current_zone_set = next(serials) - mock.disable_delay = parameters.get("disable-delay", 0) - mock.activated_delay = parameters.get("activated-delay", 0) - mock.deactivated_delay = parameters.get("deactivated-delay", 0) - mock.AddProperties( MAIN_IFACE, dbus.Dictionary( { "version": dbus.UInt32(parameters.get("version", VERSION)), - "SupportedCapabilities": dbus.UInt32(mock.supported_capabilities), + "SupportedCapabilities": dbus.UInt32( + mock.inputcapture_params.supported_capabilities + ), } ), ) @@ -64,16 +83,17 @@ def load(mock, parameters=None): def CreateSession(self, handle, session_handle, app_id, parent_window, options): try: logger.debug(f"CreateSession({parent_window}, {options})") + params = self.inputcapture_params assert "capabilities" in options # Filter to the subset of supported capabilities - if self.capabilities is None: + if params.capabilities is None: capabilities = options["capabilities"] else: - capabilities = self.capabilities + capabilities = params.capabilities - capabilities &= self.supported_capabilities + capabilities &= params.supported_capabilities response = Response(0, {}) response.results["capabilities"] = dbus.UInt32(capabilities) @@ -95,11 +115,12 @@ def CreateSession(self, handle, session_handle, app_id, parent_window, options): def GetZones(self, handle, session_handle, app_id, options): try: logger.debug(f"GetZones({session_handle}, {options})") + params = self.inputcapture_params assert session_handle in self.active_session_handles response = Response(0, {}) - response.results["zones"] = self.default_zone + response.results["zones"] = params.default_zone response.results["zone_set"] = dbus.UInt32( self.current_zone_set, variant_level=1 ) @@ -191,6 +212,7 @@ def SetPointerBarriers( def Enable(self, session_handle, app_id, options): try: logger.debug(f"Enable({session_handle}, {options})") + params = self.inputcapture_params assert session_handle in self.active_session_handles @@ -199,15 +221,15 @@ def Enable(self, session_handle, app_id, options): barrier = self.current_barriers[0] pos = (barrier.position[0] + 10, barrier.position[1] + 20) - if self.disable_delay > 0: + if params.disable_delay > 0: def disable(): logger.debug("emitting Disabled") - self.EmitSignal("", "Disabled", "oa{sv}", [session_handle, {}]) + self.EmitSignal(MAIN_IFACE, "Disabled", "oa{sv}", [session_handle, {}]) - GLib.timeout_add(self.disable_delay, disable) + GLib.timeout_add(params.disable_delay, disable) - if self.activated_delay > 0: + if params.activated_delay > 0: def activated(): logger.debug("emitting Activated") @@ -218,11 +240,13 @@ def activated(): pos, signature="dd", variant_level=1 ), } - self.EmitSignal("", "Activated", "oa{sv}", [session_handle, options]) + self.EmitSignal( + MAIN_IFACE, "Activated", "oa{sv}", [session_handle, options] + ) - GLib.timeout_add(self.activated_delay, activated) + GLib.timeout_add(params.activated_delay, activated) - if self.deactivated_delay > 0: + if params.deactivated_delay > 0: def deactivated(): logger.debug("emitting Deactivated") @@ -232,9 +256,24 @@ def deactivated(): pos, signature="dd", variant_level=1 ), } - self.EmitSignal("", "Deactivated", "oa{sv}", [session_handle, options]) + self.EmitSignal( + MAIN_IFACE, "Deactivated", "oa{sv}", [session_handle, options] + ) + + GLib.timeout_add(params.deactivated_delay, deactivated) + + if params.zones_changed_delay > 0: + + def zones_changed(): + logger.debug("emitting ZonesChanged") + options = { + "zone_set": dbus.UInt32(activation_id, variant_level=1), + } + self.EmitSignal( + MAIN_IFACE, "ZonesChanged", "oa{sv}", [session_handle, options] + ) - GLib.timeout_add(self.deactivated_delay, deactivated) + GLib.timeout_add(params.zones_changed_delay, zones_changed) except Exception as e: logger.critical(e) diff --git a/tests/templates/lockdown.py b/tests/templates/lockdown.py new file mode 100644 index 0000000..adcf943 --- /dev/null +++ b/tests/templates/lockdown.py @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import init_logger + +import dbus.service + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Lockdown" + + +logger = init_logger(__name__) + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "disable-printing": dbus.Boolean( + parameters.get("disable-printing", False) + ), + "disable-save-to-disk": dbus.Boolean( + parameters.get("disable-save-to-disk", False) + ), + "disable-application-handlers": dbus.Boolean( + parameters.get("disable-application-handlers", False) + ), + "disable-location": dbus.Boolean( + parameters.get("disable-location", False) + ), + "disable-camera": dbus.Boolean(parameters.get("disable-camera", False)), + "disable-microphone": dbus.Boolean( + parameters.get("disable-microphone", False) + ), + "disable-sound-output": dbus.Boolean( + parameters.get("disable-sound-output", False) + ), + } + ), + ) diff --git a/tests/templates/meson.build b/tests/templates/meson.build deleted file mode 100644 index 8a1a6a4..0000000 --- a/tests/templates/meson.build +++ /dev/null @@ -1,16 +0,0 @@ -template_files = [ - '__init__.py', - 'clipboard.py', - 'email.py', - 'globalshortcuts.py', - 'inputcapture.py', - 'remotedesktop.py', -] -foreach template_file : template_files - configure_file( - input: template_file, - output: template_file, - copy: true, - install: false - ) -endforeach diff --git a/tests/templates/notification.py b/tests/templates/notification.py new file mode 100644 index 0000000..730061c --- /dev/null +++ b/tests/templates/notification.py @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import init_logger + +import dbus.service +from dbusmock import MOCK_IFACE + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Notification" +VERSION = 2 + + +logger = init_logger(__name__) + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + "SupportedOptions": dbus.Dictionary( + parameters.get("SupportedOptions", {}), signature="sv" + ), + }, + ), + ) + mock.notifications = {} + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ssa{sv}", + out_signature="", +) +def AddNotification(self, app_id, id, notification): + logger.debug(f"AddNotification({app_id}, {id}, {notification})") + + self.notifications.setdefault(app_id, {})[id] = notification + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ss", + out_signature="", +) +def RemoveNotification(self, app_id, id): + logger.debug(f"AddNotification({app_id}, {id})") + + del self.notifications[app_id][id] + + +@dbus.service.method( + MOCK_IFACE, + in_signature="sssav", + out_signature="", +) +def EmitActionInvoked(self, app_id, id, action, parameter): + logger.debug(f"EmitActionInvoked({app_id}, {id}, {action}, {parameter})") + + # n = self.notifications[app_id][id] + # FIXME check action is in n + + self.EmitSignal( + MAIN_IFACE, + "ActionInvoked", + "sssav", + [ + app_id, + id, + action, + dbus.Array(parameter, signature="v"), + ], + ) diff --git a/tests/templates/print.py b/tests/templates/print.py new file mode 100644 index 0000000..c3475f5 --- /dev/null +++ b/tests/templates/print.py @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus.service +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Print" +VERSION = 3 + + +logger = init_logger(__name__) + + +@dataclass +class PrintParameters: + delay: int + response: int + results: dict + expect_close: bool + prepare_results: dict + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "print_params") + mock.print_params = PrintParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + results=parameters.get("results", {}), + expect_close=parameters.get("expect-close", False), + prepare_results=parameters.get("prepare-results", {}), + ) + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + } + ), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="osssa{sv}a{sv}a{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def PreparePrint( + self, + handle, + app_id, + parent_window, + title, + settings, + page_setup, + options, + cb_success, + cb_error, +): + logger.debug( + f"PreparePrint({handle}, {app_id}, {parent_window}, {title}, {settings}, {page_setup}, {options})" + ) + params = self.print_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond( + Response(params.response, params.prepare_results), delay=params.delay + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="osssha{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def Print( + self, handle, app_id, parent_window, title, fd, options, cb_success, cb_error +): + logger.debug( + f"Print({handle}, {app_id}, {parent_window}, {title}, {fd}, {options})" + ) + params = self.print_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, params.results), delay=params.delay) diff --git a/tests/templates/remotedesktop.py b/tests/templates/remotedesktop.py index e30e7e4..3030fb2 100644 --- a/tests/templates/remotedesktop.py +++ b/tests/templates/remotedesktop.py @@ -1,13 +1,16 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # This file is formatted with Python Black +# mypy: disable-error-code="misc" -from tests.templates import Response, init_template_logger, ImplRequest, ImplSession +from tests.templates import Response, init_logger, ImplRequest, ImplSession + +from dbusmock import MOCK_IFACE import dbus import dbus.service import socket - from gi.repository import GLib +from dataclasses import dataclass BUS_NAME = "org.freedesktop.impl.portal.Test" @@ -17,20 +20,32 @@ VERSION = 2 -logger = init_template_logger(__name__) +logger = init_logger(__name__) + +@dataclass +class RemotedesktopParameters: + delay: int + response: int + expect_close: bool + force_close: int + force_clipoboard_enabled: bool + fail_connect_to_eis: bool -def load(mock, parameters): + +def load(mock, parameters={}): logger.debug(f"Loading parameters: {parameters}") - mock.delay: int = parameters.get("delay", 200) - mock.response: int = parameters.get("response", 0) - mock.expect_close: bool = parameters.get("expect-close", False) - mock.force_close: int = parameters.get("force-close", 0) - mock.force_clipoboard_enabled: bool = parameters.get( - "force-clipboard-enabled", False + assert not hasattr(mock, "remotedesktop_params") + mock.remotedesktop_params = RemotedesktopParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + force_close=parameters.get("force-close", 0), + force_clipoboard_enabled=parameters.get("force-clipboard-enabled", False), + fail_connect_to_eis=parameters.get("fail-connect-to-eis", False), ) - mock.fail_connect_to_eis: bool = parameters.get("fail-connect-to-eis", False) + mock.AddProperties( MAIN_IFACE, dbus.Dictionary( @@ -49,44 +64,30 @@ def load(mock, parameters): async_callbacks=("cb_success", "cb_error"), ) def CreateSession(self, handle, session_handle, app_id, options, cb_success, cb_error): - try: - logger.debug(f"CreateSession({handle}, {session_handle}, {app_id}, {options})") - - session = ImplSession(self, BUS_NAME, session_handle).export() - self.sessions[session_handle] = session - - response = Response(self.response, {"session_handle": session.handle}) - - request = ImplRequest(self, BUS_NAME, handle) - - if self.expect_close: - - def closed_callback(): - response = Response(2, {}) - logger.debug(f"CreateSession Close() response {response}") - cb_success(response.response, response.results) - - request.export(closed_callback) - else: - request.export() - - def reply(): - logger.debug(f"CreateSession with response {response}") - cb_success(response.response, response.results) - - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, reply) - - if self.force_close > 0: - - def force_close(): - session.close() - - GLib.timeout_add(self.force_close, force_close) + logger.debug(f"CreateSession({handle}, {session_handle}, {app_id}, {options})") + params = self.remotedesktop_params + + session = ImplSession(self, BUS_NAME, session_handle, app_id).export() + self.sessions[session_handle] = session + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) - except Exception as e: - logger.critical(e) - cb_error(e) + if params.expect_close: + request.wait_for_close() + else: + request.respond( + Response(params.response, {"session_handle": session.handle}), + delay=params.delay, + ) + if params.force_close > 0: + GLib.timeout_add(params.force_close, session.close) @dbus.service.method( @@ -96,24 +97,24 @@ def force_close(): async_callbacks=("cb_success", "cb_error"), ) def SelectDevices(self, handle, session_handle, app_id, options, cb_success, cb_error): - try: - logger.debug(f"SelectDevices({handle}, {session_handle}, {app_id}, {options})") - - assert session_handle in self.sessions - response = Response(self.response, {}) - request = ImplRequest(self, BUS_NAME, handle) - request.export() - - def reply(): - logger.debug(f"SelectDevices with response {response}") - cb_success(response.response, response.results) + logger.debug(f"SelectDevices({handle}, {session_handle}, {app_id}, {options})") + params = self.remotedesktop_params + + assert session_handle in self.sessions + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, reply) - - except Exception as e: - logger.critical(e) - cb_error(e) + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, {}), delay=params.delay) @dbus.service.method( @@ -125,40 +126,30 @@ def reply(): def Start( self, handle, session_handle, app_id, parent_window, options, cb_success, cb_error ): - try: - logger.debug( - f"Start({handle}, {session_handle}, {parent_window}, {app_id}, {options})" - ) - - assert session_handle in self.sessions - response = Response(self.response, {}) - - if self.force_clipoboard_enabled: - response.results["clipboard_enabled"] = True - - request = ImplRequest(self, BUS_NAME, handle) + logger.debug( + f"Start({handle}, {session_handle}, {parent_window}, {app_id}, {options})" + ) + params = self.remotedesktop_params - if self.expect_close: + assert session_handle in self.sessions - def closed_callback(): - response = Response(2, {}) - logger.debug(f"Start Close() response {response}") - cb_success(response.response, response.results) + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) - request.export(closed_callback) - else: - request.export() + response = Response(params.response, {}) + if params.force_clipoboard_enabled: + response.results["clipboard_enabled"] = True - def reply(): - logger.debug(f"Start with response {response}") - cb_success(response.response, response.results) - - logger.debug(f"scheduling delay of {self.delay}") - GLib.timeout_add(self.delay, reply) - - except Exception as e: - logger.critical(e) - cb_error(e) + if params.expect_close: + request.wait_for_close() + else: + request.respond(response, delay=params.delay) @dbus.service.method( @@ -169,10 +160,11 @@ def reply(): def ConnectToEIS(self, session_handle, app_id, options): try: logger.debug(f"ConnectToEIS({session_handle}, {app_id}, {options})") + params = self.remotedesktop_params assert session_handle in self.sessions - if self.fail_connect_to_eis: + if params.fail_connect_to_eis: raise dbus.exceptions.DBusException("Purposely failing ConnectToEIS") sockets = socket.socketpair() @@ -183,3 +175,11 @@ def ConnectToEIS(self, session_handle, app_id, options): except Exception as e: logger.critical(e) raise e + + +@dbus.service.method(MOCK_IFACE, in_signature="s", out_signature="s") +def GetSessionAppId(self, session_handle): + logger.debug(f"GetSessionAppId({session_handle})") + + assert session_handle in self.sessions + return self.sessions[session_handle].app_id diff --git a/tests/templates/screenshot.py b/tests/templates/screenshot.py new file mode 100644 index 0000000..1610175 --- /dev/null +++ b/tests/templates/screenshot.py @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus.service +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Screenshot" +VERSION = 2 + + +logger = init_logger(__name__) + + +@dataclass +class ScreenshotParameters: + delay: int + response: int + results: dict + expect_close: bool + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "screenshot_params") + mock.screenshot_params = ScreenshotParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + results=parameters.get("results", {}), + expect_close=parameters.get("expect-close", False), + ) + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + } + ), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ossa{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def Screenshot(self, handle, app_id, parent_window, options, cb_success, cb_error): + logger.debug(f"Screenshot({handle}, {app_id}, {parent_window}, {options})") + params = self.screenshot_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, params.results), delay=params.delay) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ossa{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def PickColor(self, handle, app_id, parent_window, options, cb_success, cb_error): + logger.debug(f"PickColor({handle}, {app_id}, {parent_window}, {options})") + params = self.screenshot_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, params.results), delay=params.delay) diff --git a/tests/templates/settings.py b/tests/templates/settings.py new file mode 100644 index 0000000..7a40773 --- /dev/null +++ b/tests/templates/settings.py @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import init_logger + +import dbus.service +from dbusmock import MOCK_IFACE +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Settings" +VERSION = 2 + + +logger = init_logger(__name__) + + +@dataclass +class SettingsParameters: + settings: dict + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "settings_params") + mock.settings_params = SettingsParameters( + settings=parameters.get("settings", {}), + ) + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + } + ), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="as", + out_signature="a{sa{sv}}", +) +def ReadAll(self, namespaces): + logger.debug(f"ReadAll({namespaces})") + settings = self.settings_params.settings + + if len(namespaces) == 0 or (len(namespaces) == 1 and namespaces[0] == ""): + return settings + + def find_matching(namespace): + if len(namespace) >= 3 and namespace[-2:] == ".*": + ns_prefix = namespace[:-2] + matches = {} + for ns in settings: + if ns.startswith(ns_prefix): + matches[ns] = settings[ns] + return matches + + if namespace in settings: + return {namespace: settings[namespace]} + + return {} + + result = dbus.Dictionary({}, signature="sa{sv}") + for ns in namespaces: + result |= find_matching(ns) + + return result + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ss", + out_signature="v", +) +def Read(self, namespace, key): + logger.debug(f"Read({namespace}, {key})") + settings = self.settings_params.settings + + return settings[namespace][key] + + +@dbus.service.method( + MOCK_IFACE, + in_signature="ssv", + out_signature="", +) +def SetSetting(self, namespace, key, value): + logger.debug(f"SetSetting({namespace}, {key}, {value})") + settings = self.settings_params.settings + + settings.setdefault(namespace, {})[key] = value + + self.EmitSignal( + MAIN_IFACE, + "SettingChanged", + "ssv", + [namespace, key, value], + ) diff --git a/tests/templates/usb.py b/tests/templates/usb.py new file mode 100644 index 0000000..1290200 --- /dev/null +++ b/tests/templates/usb.py @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus +import dbus.service +from dbusmock import MOCK_IFACE +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Usb" +VERSION = 1 + + +logger = init_logger(__name__) + + +@dataclass +class UsbParameters: + delay: int + response: int + expect_close: bool + filters: dict + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "usb_params") + mock.usb_params = UsbParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + filters=parameters.get("filters", {}), + ) + + mock.AddProperties( + MAIN_IFACE, + dbus.Dictionary( + { + "version": dbus.UInt32(parameters.get("version", VERSION)), + } + ), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="ossa(sa{sv}a{sv})a{sv}", + out_signature="ua{sv}", + async_callbacks=("cb_success", "cb_error"), +) +def AcquireDevices( + self, + handle, + parent_window, + app_id, + devices, + options, + cb_success, + cb_error, +): + logger.debug( + f"AcquireDevices({handle}, {parent_window}, {app_id}, {devices}, {options})" + ) + params = self.usb_params + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + cb_success, + cb_error, + ) + + def reply(): + # no options supported + assert not options + devices_out = [] + + for device in devices: + (id, info, access_options) = device + props = info["properties"] + + allows_writable = params.filters.get("writable", True) + needs_writable = access_options.get("writable", False) + if needs_writable and not allows_writable: + logger.debug(f"Skipping device {id} because it requires writable") + continue + + needs_vendor = params.filters.get("vendor", None) + needs_vendor = int(needs_vendor, 16) if needs_vendor else None + + vendor = props.get("ID_VENDOR_ID", None) + vendor = int(vendor, 16) if vendor else None + + if needs_vendor is not None and needs_vendor != vendor: + logger.debug( + f"Skipping device {id} because it does not belong to vendor {needs_vendor:02x}" + ) + continue + + needs_model = params.filters.get("model", None) + needs_model = int(needs_model, 16) if needs_model else None + + model = props.get("ID_MODEL_ID", None) + model = int(model, 16) if model else None + + if needs_model is not None and needs_model != model: + logger.debug( + f"Skipping device {id} because it is not a model {needs_model:02x}" + ) + continue + + devices_out.append( + dbus.Struct([id, access_options], signature="sa{sv}", variant_level=1) + ) + + return Response( + params.response, + {"devices": dbus.Array(devices_out, signature="(sa{sv})", variant_level=1)}, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(reply, delay=params.delay) + + +@dbus.service.method( + MOCK_IFACE, + in_signature="a{sv}", + out_signature="", +) +def SetSelectionFilters(self, filters): + logger.debug(f"SetSelectionFilters({filters})") + params = self.usb_params + + params.filters = filters diff --git a/tests/templates/wallpaper.py b/tests/templates/wallpaper.py new file mode 100644 index 0000000..0b81339 --- /dev/null +++ b/tests/templates/wallpaper.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black +# mypy: disable-error-code="misc" + +from tests.templates import Response, init_logger, ImplRequest + +import dbus +import dbus.service +from dataclasses import dataclass + + +BUS_NAME = "org.freedesktop.impl.portal.Test" +MAIN_OBJ = "/org/freedesktop/portal/desktop" +SYSTEM_BUS = False +MAIN_IFACE = "org.freedesktop.impl.portal.Wallpaper" + + +logger = init_logger(__name__) + + +@dataclass +class WallpaperParameters: + delay: int + response: int + expect_close: bool + + +def load(mock, parameters={}): + logger.debug(f"Loading parameters: {parameters}") + + assert not hasattr(mock, "wallpaper_params") + mock.wallpaper_params = WallpaperParameters( + delay=parameters.get("delay", 200), + response=parameters.get("response", 0), + expect_close=parameters.get("expect-close", False), + ) + + +@dbus.service.method( + MAIN_IFACE, + in_signature="osssa{sv}", + out_signature="u", + async_callbacks=("cb_success", "cb_error"), +) +def SetWallpaperURI( + self, handle, app_id, parent_window, uri, options, cb_success, cb_error +): + logger.debug( + f"SetWallpaperURI({handle}, {app_id}, {parent_window}, {uri}, {options})" + ) + params = self.wallpaper_params + + # This is irregular: the backend doesn't return the results vardict + def internal_cb_success(response, results): + cb_success(response) + + request = ImplRequest( + self, + BUS_NAME, + handle, + logger, + internal_cb_success, + cb_error, + ) + + if params.expect_close: + request.wait_for_close() + else: + request.respond(Response(params.response, {}), delay=params.delay) diff --git a/tests/test-doc-portal.c b/tests/test-doc-portal.c deleted file mode 100644 index 8298943..0000000 --- a/tests/test-doc-portal.c +++ /dev/null @@ -1,882 +0,0 @@ -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "document-portal/document-portal-dbus.h" - -#include "can-use-fuse.h" -#include "src/glib-backports.h" -#include "utils.h" - -char outdir[] = "/tmp/xdp-test-XXXXXX"; - -char fuse_status_file[] = "/tmp/test-xdp-fuse-XXXXXX"; - -GTestDBus *dbus; -GDBusConnection *session_bus; -XdpDbusDocuments *documents; -char *mountpoint; - -static gboolean -set_contents_trunc (const gchar *filename, - const gchar *contents, - gssize length, - GError **error) -{ - int fd; - - if (length == -1) - length = strlen (contents); - - fd = open (filename, O_RDWR | O_TRUNC | O_CREAT, 0644); - if (fd == -1) - { - int errsv = errno; - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errsv), - "Can't open %s", filename); - return FALSE; - } - - while (length > 0) - { - gssize s; - - s = write (fd, contents, length); - - if (s < 0) - { - int errsv = errno; - if (errsv == EINTR) - continue; - - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errsv), - "Can't write to %s", filename); - close (fd); - return FALSE; - } - - contents += s; - length -= s; - } - - close (fd); - return TRUE; -} - -static char * -make_doc_dir (const char *id, const char *app) -{ - if (app) - return g_build_filename (mountpoint, "by-app", app, id, NULL); - else - return g_build_filename (mountpoint, id, NULL); -} - -static char * -make_doc_path (const char *id, const char *basename, const char *app) -{ - g_autofree char *dir = make_doc_dir (id, app); - - return g_build_filename (dir, basename, NULL); -} - -static void -assert_host_has_contents (const char *basename, const char *expected_contents) -{ - g_autofree char *path = g_build_filename (outdir, basename, NULL); - g_autofree char *real_contents = NULL; - gsize real_contents_length; - GError *error = NULL; - - g_file_get_contents (path, &real_contents, &real_contents_length, &error); - g_assert_no_error (error); - g_assert_cmpstr (real_contents, ==, expected_contents); - g_assert_cmpuint (real_contents_length, ==, strlen (expected_contents)); -} - -static void -assert_doc_has_contents (const char *id, const char *basename, const char *app, const char *expected_contents) -{ - g_autofree char *path = make_doc_path (id, basename, app); - g_autofree char *real_contents = NULL; - gsize real_contents_length; - GError *error = NULL; - - g_file_get_contents (path, &real_contents, &real_contents_length, &error); - g_assert_no_error (error); - g_assert_cmpstr (real_contents, ==, expected_contents); - g_assert_cmpuint (real_contents_length, ==, strlen (expected_contents)); -} - -static void -assert_doc_not_exist (const char *id, const char *basename, const char *app) -{ - g_autofree char *path = make_doc_path (id, basename, app); - struct stat buf; - int res, fd; - - res = stat (path, &buf); - g_assert_cmpint (res, ==, -1); - g_assert_cmpint (errno, ==, ENOENT); - - fd = open (path, O_RDONLY); - g_assert_cmpint (fd, ==, -1); - g_assert_cmpint (errno, ==, ENOENT); -} - -static void -assert_doc_dir_not_exist (const char *id, const char *app) -{ - g_autofree char *path = make_doc_dir (id, app); - struct stat buf; - int res, fd; - - res = stat (path, &buf); - g_assert_cmpint (res, ==, -1); - g_assert_cmpint (errno, ==, ENOENT); - - fd = open (path, O_RDONLY); - g_assert_cmpint (fd, ==, -1); - g_assert_cmpint (errno, ==, ENOENT); -} - -static void -assert_doc_dir_exist (const char *id, const char *app) -{ - g_autofree char *path = make_doc_dir (id, app); - struct stat buf; - int res, fd; - - res = stat (path, &buf); - g_assert_cmpint (res, ==, 0); - - fd = open (path, O_RDONLY); - g_assert_cmpint (fd, !=, -1); - close (fd); -} - -static char * -export_named_file (const char *dir, const char *name, gboolean unique) -{ - int fd, fd_id; - GUnixFDList *fd_list = NULL; - - g_autoptr(GVariant) reply = NULL; - GError *error = NULL; - char *doc_id; - - fd = open (dir, O_PATH | O_CLOEXEC); - g_assert (fd >= 0); - - fd_list = g_unix_fd_list_new (); - fd_id = g_unix_fd_list_append (fd_list, fd, &error); - g_assert_no_error (error); - close (fd); - - reply = g_dbus_connection_call_with_unix_fd_list_sync (session_bus, - "org.freedesktop.portal.Documents", - "/org/freedesktop/portal/documents", - "org.freedesktop.portal.Documents", - "AddNamed", - g_variant_new ("(h^aybb)", fd_id, name, !unique, FALSE), - G_VARIANT_TYPE ("(s)"), - G_DBUS_CALL_FLAGS_NONE, - 30000, - fd_list, NULL, - NULL, - &error); - g_object_unref (fd_list); - g_assert_no_error (error); - g_assert (reply != NULL); - - g_variant_get (reply, "(s)", &doc_id); - g_assert (doc_id != NULL); - return doc_id; -} - -static char * -export_file (const char *path, gboolean unique) -{ - int fd, fd_id; - GUnixFDList *fd_list = NULL; - - g_autoptr(GVariant) reply = NULL; - GError *error = NULL; - char *doc_id; - - fd = open (path, O_PATH | O_CLOEXEC); - g_assert (fd >= 0); - - fd_list = g_unix_fd_list_new (); - fd_id = g_unix_fd_list_append (fd_list, fd, &error); - g_assert_no_error (error); - close (fd); - - reply = g_dbus_connection_call_with_unix_fd_list_sync (session_bus, - "org.freedesktop.portal.Documents", - "/org/freedesktop/portal/documents", - "org.freedesktop.portal.Documents", - "Add", - g_variant_new ("(hbb)", fd_id, !unique, FALSE), - G_VARIANT_TYPE ("(s)"), - G_DBUS_CALL_FLAGS_NONE, - 30000, - fd_list, NULL, - NULL, - &error); - g_object_unref (fd_list); - g_assert_no_error (error); - g_assert (reply != NULL); - - g_variant_get (reply, "(s)", &doc_id); - g_assert (doc_id != NULL); - return doc_id; -} - -static char * -export_new_file (const char *basename, const char *contents, gboolean unique) -{ - g_autofree char *path = NULL; - GError *error = NULL; - - path = g_build_filename (outdir, basename, NULL); - - g_file_set_contents (path, contents, -1, &error); - g_assert_no_error (error); - - return export_file (path, unique); -} - -static gboolean -update_doc_trunc (const char *id, const char *basename, const char *app, const char *contents, GError **error) -{ - g_autofree char *path = make_doc_path (id, basename, app); - - return set_contents_trunc (path, contents, -1, error); -} - -static gboolean -update_doc (const char *id, const char *basename, const char *app, const char *contents, GError **error) -{ - g_autofree char *path = make_doc_path (id, basename, app); - - return g_file_set_contents (path, contents, -1, error); -} - -static gboolean -update_from_host (const char *basename, const char *contents, GError **error) -{ - g_autofree char *path = g_build_filename (outdir, basename, NULL); - - return g_file_set_contents (path, contents, -1, error); -} - -static gboolean -unlink_doc (const char *id, const char *basename, const char *app, GError **error) -{ - g_autofree char *path = make_doc_path (id, basename, app); - - if (unlink (path) != 0) - { - int errsv = errno; - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errsv), - "Can't unlink %s", path); - return FALSE; - } - - return TRUE; -} - -static gboolean -unlink_doc_from_host (const char *basename, GError **error) -{ - g_autofree char *path = g_build_filename (outdir, basename, NULL); - - if (unlink (path) != 0) - { - int errsv = errno; - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errsv), - "Can't unlink %s", path); - return FALSE; - } - - return TRUE; -} - -static void -grant_permissions (const char *id, const char *app, gboolean write) -{ - g_autoptr(GPtrArray) permissions = g_ptr_array_new (); - GError *error = NULL; - - g_ptr_array_add (permissions, "read"); - if (write) - g_ptr_array_add (permissions, "write"); - g_ptr_array_add (permissions, NULL); - - xdp_dbus_documents_call_grant_permissions_sync (documents, - id, - app, - (const char **) permissions->pdata, - NULL, - &error); - g_assert_no_error (error); -} - -static void -test_create_doc (void) -{ - g_autofree char *doc_path = NULL; - g_autofree char *doc_app_path = NULL; - g_autofree char *host_path = NULL; - g_autofree char *id = NULL; - g_autofree char *id2 = NULL; - g_autofree char *id3 = NULL; - g_autofree char *id4 = NULL; - g_autofree char *id5 = NULL; - const char *basename = "a-file"; - GError *error = NULL; - - if (!check_fuse_or_skip_test ()) - return; - - /* Export a document */ - id = export_new_file (basename, "content", FALSE); - - /* Ensure its there and not viewable by apps */ - assert_doc_has_contents (id, basename, NULL, "content"); - assert_host_has_contents (basename, "content"); - assert_doc_not_exist (id, basename, "com.test.App1"); - assert_doc_not_exist (id, basename, "com.test.App2"); - assert_doc_not_exist (id, "another-file", NULL); - assert_doc_not_exist ("anotherid", basename, NULL); - - /* Create a tmp file in same dir, ensure it works and can't be seen by other apps */ - assert_doc_not_exist (id, "tmp1", NULL); - update_doc (id, "tmp1", NULL, "tmpdata1", &error); - g_assert_no_error (error); - assert_doc_has_contents (id, "tmp1", NULL, "tmpdata1"); - assert_doc_not_exist (id, "tmp1", "com.test.App1"); - - /* Let App 1 see the document (but not write) */ - grant_permissions (id, "com.test.App1", FALSE); - - /* Ensure App 1 and only it can see the document and tmpfile */ - assert_doc_has_contents (id, basename, "com.test.App1", "content"); - assert_doc_not_exist (id, basename, "com.test.App2"); - assert_doc_not_exist (id, "tmp1", "com.test.App1"); - - /* Make sure App 1 can't create a tmpfile */ - assert_doc_not_exist (id, "tmp2", "com.test.App1"); - update_doc (id, "tmp2", "com.test.App1", "tmpdata2", &error); - g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES); - g_clear_error (&error); - assert_doc_not_exist (id, "tmp2", "com.test.App1"); - - /* Update the document contents, ensure this is propagated */ - update_doc (id, basename, NULL, "content2", &error); - g_assert_no_error (error); - - assert_host_has_contents (basename, "content2"); - assert_doc_has_contents (id, basename, NULL, "content2"); - assert_doc_has_contents (id, basename, "com.test.App1", "content2"); - assert_doc_not_exist (id, basename, "com.test.App2"); - assert_doc_not_exist (id, "tmp1", "com.test.App2"); - - /* Update the document contents outside fuse fd, ensure this is propagated */ - update_from_host (basename, "content3", &error); - g_assert_no_error (error); - assert_host_has_contents (basename, "content3"); - assert_doc_has_contents (id, basename, NULL, "content3"); - assert_doc_has_contents (id, basename, "com.test.App1", "content3"); - assert_doc_not_exist (id, basename, "com.test.App2"); - assert_doc_not_exist (id, "tmp1", "com.test.App2"); - - /* Try to update the doc from an app that can't write to it */ - update_doc (id, basename, "com.test.App1", "content4", &error); - g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES); - g_clear_error (&error); - - /* Try to create a tmp file for an app that is not allowed */ - assert_doc_not_exist (id, "tmp2", "com.test.App1"); - update_doc (id, "tmp2", "com.test.App1", "tmpdata2", &error); - g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES); - g_clear_error (&error); - assert_doc_not_exist (id, "tmp2", "com.test.App1"); - assert_doc_not_exist (id, "tmp2", NULL); - - /* Grant write permissions to App1 */ - grant_permissions (id, "com.test.App1", TRUE); - - /* update the doc from an app with write access */ - update_doc (id, basename, "com.test.App1", "content5", &error); - g_assert_no_error (error); - assert_host_has_contents (basename, "content5"); - assert_doc_has_contents (id, basename, NULL, "content5"); - assert_doc_has_contents (id, basename, "com.test.App1", "content5"); - assert_doc_not_exist (id, basename, "com.test.App2"); - - /* Try to create a tmp file for an app */ - assert_doc_not_exist (id, "tmp3", "com.test.App1"); - update_doc (id, "tmp3", "com.test.App1", "tmpdata3", &error); - g_assert_no_error (error); - assert_doc_has_contents (id, "tmp3", "com.test.App1", "tmpdata3"); - assert_doc_not_exist (id, "tmp3", NULL); - - /* Re-Create a file from a fuse document file, in various ways */ - doc_path = make_doc_path (id, basename, NULL); - doc_app_path = make_doc_path (id, basename, "com.test.App1"); - host_path = g_build_filename (outdir, basename, NULL); - id2 = export_file (doc_path, FALSE); - g_assert_cmpstr (id, ==, id2); - id3 = export_file (doc_app_path, FALSE); - g_assert_cmpstr (id, ==, id3); - id4 = export_file (host_path, FALSE); - g_assert_cmpstr (id, ==, id4); - - /* Ensure we can make a unique document */ - id5 = export_file (host_path, TRUE); - g_assert_cmpstr (id, !=, id5); -} - -static void -test_recursive_doc (void) -{ - g_autofree char *id = NULL; - g_autofree char *id2 = NULL; - g_autofree char *id3 = NULL; - const char *basename = "recursive-file"; - g_autofree char *path = NULL; - g_autofree char *app_path = NULL; - - if (!check_fuse_or_skip_test ()) - return; - - id = export_new_file (basename, "recursive-content", FALSE); - - assert_doc_has_contents (id, basename, NULL, "recursive-content"); - - path = make_doc_path (id, basename, NULL); - g_debug ("path: %s\n", path); - - id2 = export_file (path, FALSE); - - g_assert_cmpstr (id, ==, id2); - - grant_permissions (id, "com.test.App1", FALSE); - - app_path = make_doc_path (id, basename, "com.test.App1"); - - id3 = export_file (app_path, FALSE); - - g_assert_cmpstr (id, ==, id3); -} - -static void -test_create_docs (void) -{ - GError *error = NULL; - g_autofree char *path1 = NULL; - g_autofree char *path2 = NULL; - int fd1, fd2; - guint32 fd_ids[2]; - g_autoptr(GUnixFDList) fd_list = NULL; - gboolean res; - g_auto(GStrv) out_doc_ids = NULL; - g_autoptr(GVariant) out_extra = NULL; - const char *permissions[] = { "read", NULL }; - const char *basenames[] = { "doc1", "doc2" }; - int i; - - if (!check_fuse_or_skip_test ()) - return; - - path1 = g_build_filename (outdir, basenames[0], NULL); - g_file_set_contents (path1, basenames[0], -1, &error); - g_assert_no_error (error); - - fd1 = open (path1, O_PATH | O_CLOEXEC); - g_assert (fd1 >= 0); - - path2 = g_build_filename (outdir, basenames[1], NULL); - g_file_set_contents (path2, basenames[1], -1, &error); - g_assert_no_error (error); - - fd2 = open (path2, O_PATH | O_CLOEXEC); - g_assert (fd2 >= 0); - - fd_list = g_unix_fd_list_new (); - fd_ids[0] = g_unix_fd_list_append (fd_list, fd1, &error); - g_assert_no_error (error); - close (fd1); - fd_ids[1] = g_unix_fd_list_append (fd_list, fd2, &error); - g_assert_no_error (error); - close (fd2); - - res = xdp_dbus_documents_call_add_full_sync (documents, - g_variant_new_fixed_array (G_VARIANT_TYPE_HANDLE, - fd_ids, 2, sizeof (guint32)), - 0, - "org.other.App", - permissions, - fd_list, - &out_doc_ids, - &out_extra, - NULL, - NULL, &error); - g_assert_no_error (error); - g_assert (res); - - g_assert (g_strv_length (out_doc_ids) == 2); - for (i = 0; i < 2; i++) - { - const char *id = out_doc_ids[i]; - - /* Ensure its there and not viewable by apps */ - assert_doc_has_contents (id, basenames[i], NULL, basenames[i]); - assert_host_has_contents (basenames[i], basenames[i]); - assert_doc_not_exist (id, basenames[i], "com.test.App1"); - assert_doc_not_exist (id, basenames[i], "com.test.App2"); - assert_doc_not_exist (id, "another-file", NULL); - assert_doc_not_exist ("anotherid", basenames[i], NULL); - - assert_doc_has_contents (id, basenames[i], "org.other.App", basenames[i]); - update_doc (id, basenames[i], "org.other.App", "tmpdata2", &error); - g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES); - g_clear_error (&error); - } - g_assert (g_variant_lookup_value (out_extra, "mountpoint", G_VARIANT_TYPE_VARIANT) == 0); -} - - -static void -test_add_named (void) -{ - g_autofree char *id1 = NULL; - const char *basename1 = "add-named-1"; - GError *error = NULL; - gboolean res; - - if (!check_fuse_or_skip_test ()) - return; - - id1 = export_named_file (outdir, basename1, FALSE); - - assert_doc_dir_exist (id1, NULL); - assert_doc_dir_not_exist (id1, "com.test.App1"); - assert_doc_not_exist (id1, basename1, NULL); - assert_doc_not_exist (id1, basename1, "com.test.App1"); - - grant_permissions (id1, "com.test.App1", TRUE); - - assert_doc_dir_exist (id1, NULL); - assert_doc_dir_exist (id1, "com.test.App1"); - assert_doc_not_exist (id1, basename1, NULL); - assert_doc_not_exist (id1, basename1, "com.test.App1"); - - /* Update truncating with no previous file */ - res = update_doc_trunc (id1, basename1, NULL, "foobar", &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_has_contents (id1, basename1, NULL, "foobar"); - assert_doc_has_contents (id1, basename1, "com.test.App1", "foobar"); - assert_doc_not_exist (id1, basename1, "com.test.App2"); - - /* Update truncating with previous file */ - res = update_doc_trunc (id1, basename1, NULL, "foobar2", &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_has_contents (id1, basename1, NULL, "foobar2"); - assert_doc_has_contents (id1, basename1, "com.test.App1", "foobar2"); - assert_doc_not_exist (id1, basename1, "com.test.App2"); - - /* Update atomic with previous file */ - res = update_doc (id1, basename1, NULL, "foobar3", &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_has_contents (id1, basename1, NULL, "foobar3"); - assert_doc_has_contents (id1, basename1, "com.test.App1", "foobar3"); - assert_doc_not_exist (id1, basename1, "com.test.App2"); - - /* Update from host */ - res = update_from_host (basename1, "foobar4", &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_has_contents (id1, basename1, NULL, "foobar4"); - assert_doc_has_contents (id1, basename1, "com.test.App1", "foobar4"); - assert_doc_not_exist (id1, basename1, "com.test.App2"); - - /* Unlink doc */ - res = unlink_doc (id1, basename1, NULL, &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_dir_exist (id1, NULL); - assert_doc_dir_exist (id1, "com.test.App1"); - assert_doc_not_exist (id1, basename1, NULL); - assert_doc_not_exist (id1, basename1, "com.test.App1"); - - /* Update atomic with no previous file */ - res = update_doc (id1, basename1, NULL, "foobar5", &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_has_contents (id1, basename1, NULL, "foobar5"); - assert_doc_has_contents (id1, basename1, "com.test.App1", "foobar5"); - assert_doc_not_exist (id1, basename1, "com.test.App2"); - - /* Unlink doc on host */ - res = unlink_doc_from_host (basename1, &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_dir_exist (id1, NULL); - assert_doc_dir_exist (id1, "com.test.App1"); - assert_doc_not_exist (id1, basename1, NULL); - assert_doc_not_exist (id1, basename1, "com.test.App1"); - - /* Update atomic with unexpected no previous file */ - res = update_doc (id1, basename1, NULL, "foobar6", &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_has_contents (id1, basename1, NULL, "foobar6"); - assert_doc_has_contents (id1, basename1, "com.test.App1", "foobar6"); - assert_doc_not_exist (id1, basename1, "com.test.App2"); - - /* Unlink doc on host again */ - res = unlink_doc_from_host (basename1, &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_dir_exist (id1, NULL); - assert_doc_dir_exist (id1, "com.test.App1"); - assert_doc_not_exist (id1, basename1, NULL); - assert_doc_not_exist (id1, basename1, "com.test.App1"); - - /* Update truncating with unexpected no previous file */ - res = update_doc_trunc (id1, basename1, NULL, "foobar7", &error); - g_assert_no_error (error); - g_assert (res == TRUE); - - assert_doc_has_contents (id1, basename1, NULL, "foobar7"); - assert_doc_has_contents (id1, basename1, "com.test.App1", "foobar7"); - assert_doc_not_exist (id1, basename1, "com.test.App2"); -} - -static void -global_setup (void) -{ - gboolean inited; - GError *error = NULL; - g_autofree gchar *services = NULL; - int fd; - - if (!check_fuse ()) - { - g_assert_cmpstr (cannot_use_fuse, !=, NULL); - return; - } - - g_log_writer_default_set_use_stderr (TRUE); - - g_mkdtemp (outdir); - g_debug ("outdir: %s\n", outdir); - - fd = g_mkstemp (fuse_status_file); - close (fd); - - g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE); - g_setenv ("XDG_DATA_HOME", outdir, TRUE); - g_setenv ("TEST_DOCUMENT_PORTAL_FUSE_STATUS", fuse_status_file, TRUE); - - /* Re-defining dbus-monitor with a custom script */ - setup_dbus_daemon_wrapper (outdir); - - dbus = g_test_dbus_new (G_TEST_DBUS_NONE); - services = g_test_build_filename (G_TEST_BUILT, "services", NULL); - g_test_dbus_add_service_dir (dbus, services); - g_test_dbus_up (dbus); - - /* g_test_dbus_up unsets this, so re-set */ - g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE); - - session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); - g_assert_no_error (error); - - documents = xdp_dbus_documents_proxy_new_sync (session_bus, 0, - "org.freedesktop.portal.Documents", - "/org/freedesktop/portal/documents", - NULL, &error); - g_assert_no_error (error); - g_assert (documents != NULL); - - inited = xdp_dbus_documents_call_get_mount_point_sync (documents, &mountpoint, - NULL, &error); - g_assert_no_error (error); - g_assert (inited); - g_assert (mountpoint != NULL); -} - -static gboolean -rm_rf_dir (GFile *dir, - GError **error) -{ - g_autoptr(GFileEnumerator) enumerator = NULL; - g_autoptr(GFileInfo) child_info = NULL; - GError *temp_error = NULL; - - enumerator = g_file_enumerate_children (dir, "standard::type,standard::name", - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL, error); - if (!enumerator) - return FALSE; - - while ((child_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error))) - { - const char *name = g_file_info_get_name (child_info); - g_autoptr(GFile) child = g_file_get_child (dir, name); - - if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY) - { - if (!rm_rf_dir (child, error)) - return FALSE; - } - else - { - if (!g_file_delete (child, NULL, error)) - return FALSE; - } - - g_clear_object (&child_info); - } - - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - return FALSE; - } - - if (!g_file_delete (dir, NULL, error)) - return FALSE; - - return TRUE; -} - - -static void -global_teardown (void) -{ - GError *error = NULL; - char *argv[] = { "fusermount3", "-u", NULL, NULL }; - g_autofree char *by_app_dir = g_build_filename (mountpoint, "by-app", NULL); - struct stat buf; - g_autoptr(GFile) outdir_file = g_file_new_for_path (outdir); - int res, i; - - if (cannot_use_fuse != NULL) - return; - - res = stat (by_app_dir, &buf); - g_assert_cmpint (res, ==, 0); - - argv[2] = mountpoint; - - g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, - NULL, NULL, NULL, NULL, NULL, &error); - g_assert_no_error (error); - - res = stat (by_app_dir, &buf); - g_assert_cmpint (res, ==, -1); - g_assert_cmpint (errno, ==, ENOENT); - - for (i = 0; i < 1000; i++) - { - g_autofree char *fuse_unmount_status = NULL; - - g_file_get_contents (fuse_status_file, &fuse_unmount_status, NULL, &error); - g_assert_no_error (error); - /* Loop until something is written to the status file */ - if (strlen (fuse_unmount_status) > 0) - { - g_assert_cmpstr (fuse_unmount_status, ==, "ok"); - break; - } - g_usleep (G_USEC_PER_SEC / 100); - } - g_assert (i != 1000); /* We timed out before writing to the status file */ - (void) unlink (fuse_status_file); - - g_free (mountpoint); - - g_object_unref (documents); - - g_dbus_connection_close_sync (session_bus, NULL, &error); - g_assert_no_error (error); - - g_object_unref (session_bus); - - g_test_dbus_down (dbus); - - g_object_unref (dbus); - - res = rm_rf_dir (outdir_file, &error); - g_assert_no_error (error); -} - -static void -test_version (void) -{ - if (!check_fuse_or_skip_test ()) - return; - - g_assert_cmpint (xdp_dbus_documents_get_version (documents), ==, 4); -} - -int -main (int argc, char **argv) -{ - int res; - - /* Better leak reporting without gvfs */ - g_setenv ("GIO_USE_VFS", "local", TRUE); - - g_test_init (&argc, &argv, NULL); - - g_test_add_func ("/db/version", test_version); - g_test_add_func ("/db/create_doc", test_create_doc); - g_test_add_func ("/db/recursive_doc", test_recursive_doc); - g_test_add_func ("/db/create_docs", test_create_docs); - g_test_add_func ("/db/add_named", test_add_named); - - global_setup (); - - res = g_test_run (); - - global_teardown (); - - return res; -} diff --git a/tests/test-document-fuse.sh b/tests/test-document-fuse.sh deleted file mode 100755 index 9ea15cb..0000000 --- a/tests/test-document-fuse.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash - - -skip() { - echo "1..0 # SKIP" "$@" - exit 0 -} - -skip_without_fuse () { - fusermount3 --version >/dev/null 2>&1 || skip "no fusermount3" - - capsh --print | grep -q 'Bounding set.*[^a-z]cap_sys_admin' || \ - skip "No cap_sys_admin in bounding set, can't use FUSE" - - [ -w /dev/fuse ] || skip "no write access to /dev/fuse" - [ -e /etc/mtab ] || skip "no /etc/mtab" -} - -skip_without_fuse - -echo "1..2" - -set -e - -if [ -n "${G_TEST_SRCDIR:-}" ]; then - test_srcdir="${G_TEST_SRCDIR}" -else - test_srcdir=$(realpath "$(dirname $0)") -fi - -if [ -n "${G_TEST_BUILDDIR:-}" ]; then - test_builddir="${G_TEST_BUILDDIR}" -else - test_builddir=$(realpath "$(dirname $0)") -fi - -export TEST_DATA_DIR=`mktemp -d /tmp/xdp-XXXXXX` -mkdir -p "${TEST_DATA_DIR}/home" -mkdir -p "${TEST_DATA_DIR}/runtime" -mkdir -p "${TEST_DATA_DIR}/system" -mkdir -p "${TEST_DATA_DIR}/config" - -export HOME=${TEST_DATA_DIR}/home -export XDG_CACHE_HOME=${TEST_DATA_DIR}/home/cache -export XDG_CONFIG_HOME=${TEST_DATA_DIR}/home/config -export XDG_DATA_HOME=${TEST_DATA_DIR}/home/share -export XDG_RUNTIME_DIR=${TEST_DATA_DIR}/runtime - -cleanup () { - fusermount3 -u "$XDG_RUNTIME_DIR/doc" || : - sleep 0.1 - kill "$DBUS_SESSION_BUS_PID" - kill $(jobs -p) &> /dev/null || true - rm -rf "$TEST_DATA_DIR" -} -trap cleanup EXIT - -ITERATIONS=3 -PARALLEL_TESTS=20 -PARALLEL_ITERATIONS=10 - -if [ -n "$TEST_IN_CI" ]; then - PARALLEL_TESTS=10 - PARALLEL_ITERATIONS=5 -fi - -sed "s#@testdir@#${test_builddir}#" "${test_srcdir}/session.conf.in" > session.conf - -dbus-daemon --fork --config-file=session.conf --print-address=3 --print-pid=4 \ - 3> dbus-session-bus-address 4> dbus-session-bus-pid -export DBUS_SESSION_BUS_ADDRESS="$(cat dbus-session-bus-address)" -DBUS_SESSION_BUS_PID="$(cat dbus-session-bus-pid)" - -if ! kill -0 "$DBUS_SESSION_BUS_PID"; then - assert_not_reached "Failed to start dbus-daemon" -fi - -# Run portal manually so that we get any segfault our assert output -# Add -v here to get debug output from fuse -# Only do this when running uninstalled; when running as an installed-test, -# we rely on D-Bus activation. -if [ -n "${XDP_UNINSTALLED:-}" ]; then - $test_builddir/../document-portal/xdg-document-portal -r & - sleep 0.2 # Make sure the portal has connected to dbus -fi - -# First run a basic single-thread test -echo Testing single-threaded >&2 -"${test_srcdir}/test-document-fuse.py" --iterations ${ITERATIONS} -v -echo "ok single-threaded" - -# Then a bunch of copies in parallel to stress-test -echo Testing in parallel >&2 -PIDS=() -for i in $(seq ${PARALLEL_TESTS}); do - "${test_srcdir}/test-document-fuse.py" --iterations ${PARALLEL_ITERATIONS} --prefix "$i" & - PID="$!" - PIDS+=( "$PID" ) -done - -echo waiting for pids "${PIDS[@]}" >&2 -wait "${PIDS[@]}" -echo "ok load-test" diff --git a/tests/testdb.c b/tests/test-permission-db.c similarity index 99% rename from tests/testdb.c rename to tests/test-permission-db.c index 67f7411..e2f7697 100644 --- a/tests/testdb.c +++ b/tests/test-permission-db.c @@ -67,7 +67,7 @@ create_test_db (gboolean serialized) static void verify_test_db (PermissionDb *db) { - g_auto(GStrv) ids; + g_auto(GStrv) ids = NULL; g_autofree const char **apps1 = NULL; g_autofree const char **apps2 = NULL; g_auto(GStrv) all_apps = NULL; @@ -183,7 +183,7 @@ test_serialize (void) g_autofree char *dump2 = NULL; g_autofree char *dump3 = NULL; GError *error = NULL; - char tmpfile[] = "/tmp/testdbXXXXXX"; + char tmpfile[] = "/tmp/test-permission-db-XXXXXX"; int fd; db = create_test_db (FALSE); diff --git a/tests/test-permission-store.c b/tests/test-permission-store.c deleted file mode 100644 index 8101e1f..0000000 --- a/tests/test-permission-store.c +++ /dev/null @@ -1,695 +0,0 @@ -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "xdp-utils.h" -#include "document-portal/permission-store-dbus.h" -#include "src/glib-backports.h" -#include "utils.h" - -char outdir[] = "/tmp/xdp-test-XXXXXX"; - -GTestDBus *dbus; -GDBusConnection *session_bus; -XdgPermissionStore *permissions; - -static void -test_version (void) -{ - g_assert_cmpint (xdg_permission_store_get_version (permissions), ==, 2); -} - -static int change_count; - -static void -changed_cb (XdgPermissionStore *store, - const char *table, - const char *id, - gboolean deleted, - GVariant *data, - GVariant *perms, - gpointer user_data) -{ - g_autofree char **strv = NULL; - gboolean res; - - change_count++; - - g_assert_cmpstr (table, ==, "TEST"); - g_assert_cmpstr (id, ==, "test-resource"); - g_assert_false (deleted); - g_assert_true (g_variant_is_of_type (perms, G_VARIANT_TYPE ("a{sas}"))); - res = g_variant_lookup (perms, "one.two.three", "^a&s", &strv); - g_assert_true (res); - g_assert_cmpint (g_strv_length (strv), ==, 2); - g_assert (g_strv_contains ((const char *const *)strv, "one")); - g_assert (g_strv_contains ((const char *const *)strv, "two")); -} - -static void -changed_cb2 (XdgPermissionStore *store, - const char *table, - const char *id, - gboolean deleted, - GVariant *data, - GVariant *perms, - gpointer user_data) -{ - change_count++; - - g_assert_cmpstr (table, ==, "TEST"); - g_assert_cmpstr (id, ==, "test-resource"); - g_assert_true (deleted); -} - -static gboolean -timeout_cb (gpointer data) -{ - gboolean *timeout_reached = data; - - *timeout_reached = TRUE; - return G_SOURCE_CONTINUE; -} - -static void -test_change (void) -{ - gulong changed_handler; - gboolean res; - g_autoptr(GError) error = NULL; - const char * perms[] = { "one", "two", NULL }; - gboolean timeout_reached = FALSE; - guint timeout_id; - - changed_handler = g_signal_connect (permissions, "changed", G_CALLBACK (changed_cb), NULL); - - change_count = 0; - - res = xdg_permission_store_call_set_permission_sync (permissions, - "TEST", TRUE, - "test-resource", - "one.two.three", - perms, - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - timeout_id = g_timeout_add (10000, timeout_cb, &timeout_reached); - while (!timeout_reached && change_count == 0) - g_main_context_iteration (NULL, TRUE); - g_source_remove (timeout_id); - - g_assert_cmpint (change_count, ==, 1); - - g_signal_handler_disconnect (permissions, changed_handler); - - changed_handler = g_signal_connect (permissions, "changed", G_CALLBACK (changed_cb2), NULL); - - change_count = 0; - - res = xdg_permission_store_call_delete_sync (permissions, - "TEST", - "test-resource", - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - timeout_id = g_timeout_add (10000, timeout_cb, &timeout_reached); - while (!timeout_reached && change_count == 0) - g_main_context_iteration (NULL, TRUE); - g_source_remove (timeout_id); - - g_assert_cmpint (change_count, ==, 1); - - g_signal_handler_disconnect (permissions, changed_handler); -} - -static void -test_lookup (void) -{ - gboolean res; - g_autoptr(GError) error = NULL; - const char * perms[] = { "one", "two", NULL }; - g_autoptr(GVariant) p = NULL; - g_autoptr(GVariant) d = NULL; - g_autofree char **strv = NULL; - GVariantBuilder pb; - - res = xdg_permission_store_call_lookup_sync (permissions, - "TEST", - "test-resource", - &p, - &d, - NULL, - &error); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND); - g_assert_false (res); - g_clear_error (&error); - - g_variant_builder_init (&pb, G_VARIANT_TYPE ("a{sas}")); - g_variant_builder_add (&pb, "{s@as}", "one.two.three", g_variant_new_strv (perms, -1)); - res = xdg_permission_store_call_set_sync (permissions, - "TEST", TRUE, - "test-resource", - g_variant_builder_end (&pb), - g_variant_new_variant (g_variant_new_boolean (TRUE)), - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - res = xdg_permission_store_call_lookup_sync (permissions, - "TEST", - "test-resource", - &p, - &d, - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - g_assert_true (g_variant_is_of_type (p, G_VARIANT_TYPE ("a{sas}"))); - res = g_variant_lookup (p, "one.two.three", "^a&s", &strv); - g_assert_true (res); - g_assert_cmpint (g_strv_length (strv), ==, 2); - g_assert (g_strv_contains ((const char *const *)strv, "one")); - g_assert (g_strv_contains ((const char *const *)strv, "two")); - g_assert_true (g_variant_is_of_type (d, G_VARIANT_TYPE_VARIANT)); - g_assert_true (g_variant_is_of_type (g_variant_get_variant (d), G_VARIANT_TYPE_BOOLEAN)); - g_assert_true (g_variant_get_boolean (g_variant_get_variant (d))); - - res = xdg_permission_store_call_delete_sync (permissions, - "TEST", - "test-resource", - NULL, - &error); - g_assert_no_error (error); -} - -static void -test_set_value (void) -{ - gboolean res; - g_autoptr(GError) error = NULL; - g_autoptr(GVariant) p = NULL; - g_autoptr(GVariant) d = NULL; - - res = xdg_permission_store_call_lookup_sync (permissions, - "TEST", - "test-resource", - &p, - &d, - NULL, - &error); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND); - g_assert_false (res); - g_clear_error (&error); - - res = xdg_permission_store_call_set_value_sync (permissions, - "TEST", TRUE, - "test-resource", - g_variant_new_variant (g_variant_new_boolean (TRUE)), - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - res = xdg_permission_store_call_lookup_sync (permissions, - "TEST", - "test-resource", - &p, - &d, - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - g_assert_true (g_variant_is_of_type (p, G_VARIANT_TYPE ("a{sas}"))); - g_assert_cmpint (g_variant_n_children (p), ==, 0); - g_assert_true (res); - g_assert_true (g_variant_is_of_type (d, G_VARIANT_TYPE_VARIANT)); - g_assert_true (g_variant_is_of_type (g_variant_get_variant (d), G_VARIANT_TYPE_BOOLEAN)); - g_assert_true (g_variant_get_boolean (g_variant_get_variant (d))); - - res = xdg_permission_store_call_delete_sync (permissions, - "TEST", - "test-resource", - NULL, - &error); - g_assert_no_error (error); -} - -static void -test_create1 (void) -{ - gboolean res; - g_autoptr(GError) error = NULL; - const char * perms[] = { "one", "two", NULL }; - - res = xdg_permission_store_call_set_permission_sync (permissions, - "DOESNOTEXIST", FALSE, - "test-resource", - "one.two.three", - perms, - NULL, - &error); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND); - g_assert_false (res); -} - -static void -test_create2 (void) -{ - gboolean res; - g_autoptr(GError) error = NULL; - const char * perms[] = { "logout", "suspend", NULL }; - - res = xdg_permission_store_call_set_permission_sync (permissions, - "inhibit", - TRUE, - "inhibit", - "", - perms, - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); -} - -static void -test_delete1 (void) -{ - gboolean res; - g_autoptr(GError) error = NULL; - - res = xdg_permission_store_call_delete_sync (permissions, - "inhibit", - "no-such-entry", - NULL, - &error); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND); - g_assert_false (res); -} - -static void -test_delete2 (void) -{ - gboolean res; - g_autoptr(GError) error = NULL; - const char * perms[] = { "logout", "suspend", NULL }; - g_autoptr(GVariant) out_perms = NULL; - g_autoptr(GVariant) out_data = NULL; - - res = xdg_permission_store_call_set_permission_sync (permissions, - "inhibit", - TRUE, - "inhibit", - "", - perms, - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - res = xdg_permission_store_call_delete_sync (permissions, - "inhibit", - "inhibit", - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - res = xdg_permission_store_call_lookup_sync (permissions, - "inhibit", - "inhibit", - &out_perms, - &out_data, - NULL, - &error); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND); - g_assert_false (res); -} - -static int got_result; - -static void -set_cb (GObject *object, - GAsyncResult *result, - gpointer data) -{ - g_autoptr(GError) error = NULL; - - xdg_permission_store_call_set_permission_finish (permissions, result, &error); - g_assert_no_error (error); - - got_result++; - g_main_context_wakeup (NULL); -} - -static void -delete_cb (GObject *object, - GAsyncResult *result, - gpointer data) -{ - g_autoptr(GError) error = NULL; - - xdg_permission_store_call_delete_finish (permissions, result, &error); - g_assert_no_error (error); - - got_result++; - g_main_context_wakeup (NULL); -} - -static void -test_delete3 (void) -{ - const char * perms[] = { "logout", "suspend", NULL }; - g_autoptr(GVariant) out_perms = NULL; - g_autoptr(GVariant) out_data = NULL; - gboolean res; - g_autoptr(GError) error = NULL; - - got_result = 0; - xdg_permission_store_call_set_permission (permissions, "inhibit", TRUE, "inhibit", "", perms, NULL, set_cb, NULL); - xdg_permission_store_call_delete (permissions, "inhibit", "inhibit", NULL, delete_cb, NULL); - - while (got_result < 2) - g_main_context_iteration (NULL, TRUE); - - res = xdg_permission_store_call_lookup_sync (permissions, - "inhibit", - "inhibit", - &out_perms, - &out_data, - NULL, - &error); - g_assert_false (res); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND); -} - -static void -delete_permission_cb (GObject *object, - GAsyncResult *result, - gpointer data) -{ - g_autoptr(GError) error = NULL; - - xdg_permission_store_call_delete_permission_finish (permissions, result, &error); - g_assert_no_error (error); - - got_result++; - g_main_context_wakeup (NULL); -} - -static void -test_delete4 (void) -{ - const char * perms[] = { "logout", "suspend", NULL }; - g_autoptr(GVariant) out_perms = NULL; - g_autoptr(GVariant) out_data = NULL; - g_autoptr(GVariant) expected = NULL; - gboolean res; - g_autoptr(GError) error = NULL; - - got_result = 0; - xdg_permission_store_call_set_permission (permissions, "inhibit", TRUE, "inhibit", "a", perms, NULL, set_cb, NULL); - xdg_permission_store_call_set_permission (permissions, "inhibit", TRUE, "inhibit", "b", perms, NULL, set_cb, NULL); - xdg_permission_store_call_delete_permission (permissions, "inhibit", "inhibit", "a", NULL, delete_permission_cb, NULL); - - while (got_result < 3) - g_main_context_iteration (NULL, TRUE); - - expected = g_variant_parse (G_VARIANT_TYPE ("a{sas}"), "{\"b\": [\"logout\",\"suspend\"]}", NULL, NULL, NULL); - - res = xdg_permission_store_call_lookup_sync (permissions, - "inhibit", - "inhibit", - &out_perms, - &out_data, - NULL, - &error); - g_assert_true (res); - g_assert_no_error (error); - - g_assert_true (g_variant_equal (expected, out_perms)); -} - -static void -test_delete5 (void) -{ - const char * perms[] = { "yes", NULL }; - g_autoptr(GVariant) out_perms = NULL; - g_autoptr(GVariant) out_data = NULL; - g_autoptr(GVariant) expected = NULL; - gboolean res; - g_autoptr(GError) error = NULL; - - got_result = 0; - xdg_permission_store_call_set_permission (permissions, "notifications", TRUE, "notification", "a", perms, NULL, set_cb, NULL); - xdg_permission_store_call_delete_permission (permissions, "notifications", "notification", "a", NULL, delete_permission_cb, NULL); - - while (got_result < 2) - g_main_context_iteration (NULL, TRUE); - - /* it did not crash during delete permission */ - g_assert_cmpint (got_result, ==, 2); - - res = xdg_permission_store_call_lookup_sync (permissions, - "notifications", - "notification", - &out_perms, - &out_data, - NULL, - &error); - - - expected = g_variant_new_array (G_VARIANT_TYPE ("{sas}"), NULL, 0); - - g_assert_true (res); - g_assert_no_error (error); - - /* an empty entry is left instead */ - g_assert_true (g_variant_equal (expected, out_perms)); -} - -static void -test_get_permission1 (void) -{ - gboolean res; - g_autoptr(GError) error = NULL; - g_autofree char **out_perms = NULL; - - res = xdg_permission_store_call_get_permission_sync (permissions, - "no-such-table", - "no-such-entry", - "no-such-app", - &out_perms, - NULL, - &error); - g_assert_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND); - g_assert_false (res); -} - -static void -test_get_permission2 (void) -{ - gboolean res; - const char * in_perms[] = { "yes", NULL }; - g_auto(GStrv) out_perms = NULL; - g_autoptr(GError) error = NULL; - - res = xdg_permission_store_call_set_permission_sync (permissions, - "notifications", - TRUE, - "notification", - "a", - in_perms, - NULL, - &error); - g_assert_no_error (error); - g_assert_true (res); - - res = xdg_permission_store_call_get_permission_sync (permissions, - "notifications", - "notification", - "a", - &out_perms, - NULL, - &error); - g_assert_true (res); - g_assert_no_error (error); - g_assert (g_strv_length (out_perms) == 1); - g_assert (g_strv_contains ((const char *const *)out_perms, "yes")); -} - -static void -test_get_permission3 (void) -{ - gboolean res; - g_autofree char **out_perms = NULL; - g_autoptr(GError) error = NULL; - - res = xdg_permission_store_call_get_permission_sync (permissions, - "notifications", - "notification", - "no-such-app", - &out_perms, - NULL, - &error); - g_assert_true (res); - g_assert_no_error (error); - g_assert (g_strv_length (out_perms) == 0); -} - -static void -global_setup (void) -{ - GError *error = NULL; - g_autofree gchar *services = NULL; - GQuark portal_errors G_GNUC_UNUSED; - - /* make sure errors are registered */ - portal_errors = XDG_DESKTOP_PORTAL_ERROR; - - g_mkdtemp (outdir); - g_debug ("outdir: %s\n", outdir); - - g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE); - g_setenv ("XDG_DATA_HOME", outdir, TRUE); - - /* Re-defining dbus-monitor with a custom script */ - setup_dbus_daemon_wrapper (outdir); - - dbus = g_test_dbus_new (G_TEST_DBUS_NONE); - services = g_test_build_filename (G_TEST_BUILT, "services", NULL); - g_test_dbus_add_service_dir (dbus, services); - g_test_dbus_up (dbus); - - /* g_test_dbus_up unsets this, so re-set */ - g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE); - - session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); - g_assert_no_error (error); - - permissions = xdg_permission_store_proxy_new_sync (session_bus, 0, - "org.freedesktop.impl.portal.PermissionStore", - "/org/freedesktop/impl/portal/PermissionStore", - NULL, &error); - g_assert_no_error (error); - g_assert (permissions != NULL); -} - -static gboolean -rm_rf_dir (GFile *dir, - GError **error) -{ - g_autoptr(GFileEnumerator) enumerator = NULL; - g_autoptr(GFileInfo) child_info = NULL; - GError *temp_error = NULL; - - enumerator = g_file_enumerate_children (dir, "standard::type,standard::name", - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL, error); - if (!enumerator) - return FALSE; - - while ((child_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error))) - { - const char *name = g_file_info_get_name (child_info); - g_autoptr(GFile) child = g_file_get_child (dir, name); - - if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY) - { - if (!rm_rf_dir (child, error)) - return FALSE; - } - else - { - if (!g_file_delete (child, NULL, error)) - return FALSE; - } - - g_clear_object (&child_info); - } - - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - return FALSE; - } - - if (!g_file_delete (dir, NULL, error)) - return FALSE; - - return TRUE; -} - -static void -global_teardown (void) -{ - GError *error = NULL; - g_autoptr(GFile) outdir_file = g_file_new_for_path (outdir); - int res; - - g_object_unref (permissions); - - g_dbus_connection_close_sync (session_bus, NULL, &error); - g_assert_no_error (error); - - g_object_unref (session_bus); - - g_test_dbus_down (dbus); - - g_object_unref (dbus); - - res = rm_rf_dir (outdir_file, &error); - g_assert_no_error (error); - g_assert_true (res); -} - -int -main (int argc, char **argv) -{ - int res; - - /* Better leak reporting without gvfs */ - g_setenv ("GIO_USE_VFS", "local", TRUE); - - g_log_writer_default_set_use_stderr (TRUE); - g_test_init (&argc, &argv, NULL); - - g_test_add_func ("/permissions/version", test_version); - g_test_add_func ("/permissions/change", test_change); - g_test_add_func ("/permissions/lookup", test_lookup); - g_test_add_func ("/permissions/delete1", test_delete1); - g_test_add_func ("/permissions/delete2", test_delete2); - g_test_add_func ("/permissions/delete3", test_delete3); - g_test_add_func ("/permissions/delete4", test_delete4); - g_test_add_func ("/permissions/delete5", test_delete5); - g_test_add_func ("/permissions/create1", test_create1); - g_test_add_func ("/permissions/create2", test_create2); - g_test_add_func ("/permissions/set-value", test_set_value); - g_test_add_func ("/permissions/get-permission1", test_get_permission1); - g_test_add_func ("/permissions/get-permission2", test_get_permission2); - g_test_add_func ("/permissions/get-permission3", test_get_permission3); - - global_setup (); - - res = g_test_run (); - - global_teardown (); - - return res; -} diff --git a/tests/test-portals.c b/tests/test-portals.c deleted file mode 100644 index 3792953..0000000 --- a/tests/test-portals.c +++ /dev/null @@ -1,601 +0,0 @@ -#include -#include -#include - -#include - -#include "src/glib-backports.h" -#include "xdp-dbus.h" -#include "xdp-utils.h" -#include "xdp-impl-dbus.h" - -#ifdef HAVE_LIBPORTAL -#include "account.h" -#include "background.h" -#include "camera.h" -#include "email.h" -#include "filechooser.h" -#include "inhibit.h" -#include "location.h" -#include "notification.h" -#include "openuri.h" -#include "print.h" -#include "screenshot.h" -#include "trash.h" -#include "wallpaper.h" -#endif - -#include "utils.h" - -/* required while we support meson + autotools. Autotools builds everything in - the root dir ('.'), meson builds in each subdir nested and overrides these for - g_test_build_filename */ -#ifndef XDG_DP_BUILDDIR -#define XDG_DP_BUILDDIR "." -#endif -#ifndef XDG_PS_BUILDDIR -#define XDG_PS_BUILDDIR "." -#endif - -#define PORTAL_BUS_NAME "org.freedesktop.portal.Desktop" -#define PORTAL_OBJECT_PATH "/org/freedesktop/portal/desktop" -#define BACKEND_BUS_NAME "org.freedesktop.impl.portal.Test" -#define BACKEND_OBJECT_PATH "/org/freedesktop/portal/desktop" - -#include "document-portal/permission-store-dbus.h" - -char outdir[] = "/tmp/xdp-test-XXXXXX"; - -static GTestDBus *dbus; -static GDBusConnection *session_bus; -static GList *test_procs = NULL; -XdpDbusImplPermissionStore *permission_store; -XdpDbusImplLockdown *lockdown; - -int -xdup (int oldfd) -{ - int newfd = dup (oldfd); - - if (newfd < 0) - g_error ("Unable to duplicate fd %d: %s", oldfd, g_strerror (errno)); - - return newfd; -} - -static void -name_appeared_cb (GDBusConnection *bus, - const char *name, - const char *name_owner, - gpointer data) -{ - gboolean *b = (gboolean *)data; - - g_debug ("Name %s now owned by %s\n", name, name_owner); - - *b = TRUE; - - g_main_context_wakeup (NULL); -} - -static void -name_disappeared_cb (GDBusConnection *bus, - const char *name, - gpointer data) -{ - g_debug ("Name %s disappeared\n", name); -} - -static gboolean -timeout_cb (gpointer data) -{ - const char *msg = data; - - g_error ("%s", msg); - - return G_SOURCE_REMOVE; -} - -static void -update_data_dirs (void) -{ - const char *data_dirs; - gssize len = 0; - g_autoptr(GString) str = NULL; - - data_dirs = g_getenv ("XDG_DATA_DIRS"); - if (data_dirs != NULL && - strstr (data_dirs, "/usr/share") != NULL) - { - return; - } - - if (data_dirs != NULL) - { - len = strlen (data_dirs); - if (data_dirs[len] == ':') - len--; - } - - str = g_string_new_len (data_dirs, len); - if (str->len > 0) - g_string_append_c (str, ':'); - g_string_append (str, "/usr/local/share/:/usr/share/"); - - g_debug ("Setting XDG_DATA_DIRS to %s", str->str); - g_setenv ("XDG_DATA_DIRS", str->str, TRUE); -} - -static void -global_setup (void) -{ - GError *error = NULL; - g_autofree gchar *backends_executable = NULL; - g_autofree gchar *services = NULL; - g_autofree gchar *portal_dir = NULL; - g_autofree gchar *argv0 = NULL; - g_autoptr(GSubprocessLauncher) launcher = NULL; - g_autoptr(GSubprocess) subprocess = NULL; - guint name_timeout; - const char *argv[4]; - GQuark portal_errors G_GNUC_UNUSED; - static gboolean name_appeared; - guint watch; - guint timeout_mult = 1; - - update_data_dirs (); - - g_mkdtemp (outdir); - g_debug ("outdir: %s\n", outdir); - - g_setenv ("XDG_CURRENT_DESKTOP", "test", TRUE); - g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE); - g_setenv ("XDG_DATA_HOME", outdir, TRUE); - - /* Re-defining dbus-daemon with a custom script */ - setup_dbus_daemon_wrapper (outdir); - - dbus = g_test_dbus_new (G_TEST_DBUS_NONE); - services = g_test_build_filename (G_TEST_BUILT, "services", NULL); - g_test_dbus_add_service_dir (dbus, services); - g_test_dbus_up (dbus); - - if (g_getenv ("TEST_IN_CI")) - timeout_mult = 10; - - /* g_test_dbus_up unsets this, so re-set */ - g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE); - - session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); - g_assert_no_error (error); - - /* start portal backends */ - name_appeared = FALSE; - watch = g_bus_watch_name_on_connection (session_bus, - BACKEND_BUS_NAME, - 0, - name_appeared_cb, - name_disappeared_cb, - &name_appeared, - NULL); - - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); - g_subprocess_launcher_setenv (launcher, "G_DEBUG", "fatal-criticals", TRUE); - g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", g_test_dbus_get_bus_address (dbus), TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DATA_HOME", outdir, TRUE); - g_subprocess_launcher_setenv (launcher, "PATH", g_getenv ("PATH"), TRUE); - g_subprocess_launcher_take_stdout_fd (launcher, xdup (STDERR_FILENO)); - - backends_executable = g_test_build_filename (G_TEST_BUILT, "test-backends", NULL); - argv[0] = backends_executable; - argv[1] = "--backend-name=" BACKEND_BUS_NAME; - argv[2] = g_test_verbose () ? "--verbose" : NULL; - argv[3] = NULL; - - g_debug ("launching test-backend\n"); - - subprocess = g_subprocess_launcher_spawnv (launcher, argv, &error); - g_assert_no_error (error); - g_test_message ("Launched %s with pid %s\n", argv[0], - g_subprocess_get_identifier (subprocess)); - test_procs = g_list_append (test_procs, g_steal_pointer (&subprocess)); - - name_timeout = g_timeout_add (1000 * timeout_mult, timeout_cb, "Failed to launch test-backends"); - - while (!name_appeared) - g_main_context_iteration (NULL, TRUE); - - g_source_remove (name_timeout); - g_bus_unwatch_name (watch); - - /* start permission store */ - name_appeared = FALSE; - watch = g_bus_watch_name_on_connection (session_bus, - "org.freedesktop.impl.portal.PermissionStore", - 0, - name_appeared_cb, - name_disappeared_cb, - &name_appeared, - NULL); - - g_clear_object (&launcher); - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); - g_subprocess_launcher_setenv (launcher, "G_DEBUG", "fatal-criticals", TRUE); - g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", g_test_dbus_get_bus_address (dbus), TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DATA_HOME", outdir, TRUE); - g_subprocess_launcher_setenv (launcher, "PATH", g_getenv ("PATH"), TRUE); - g_subprocess_launcher_take_stdout_fd (launcher, xdup (STDERR_FILENO)); - - if (g_getenv ("XDP_UNINSTALLED") != NULL) - argv0 = g_test_build_filename (G_TEST_BUILT, "..", XDG_PS_BUILDDIR, "xdg-permission-store", NULL); - else - argv0 = g_strdup (LIBEXECDIR "/xdg-permission-store"); - - argv[0] = argv0; - argv[1] = "--replace"; - argv[2] = g_test_verbose () ? "--verbose" : NULL; - argv[3] = NULL; - - g_debug ("launching %s\n", argv0); - - subprocess = g_subprocess_launcher_spawnv (launcher, argv, &error); - g_assert_no_error (error); - g_test_message ("Launched %s with pid %s\n", argv[0], - g_subprocess_get_identifier (subprocess)); - test_procs = g_list_append (test_procs, g_steal_pointer (&subprocess)); - - name_timeout = g_timeout_add (1000 * timeout_mult, timeout_cb, "Failed to launch xdg-permission-store"); - - while (!name_appeared) - g_main_context_iteration (NULL, TRUE); - - g_source_remove (name_timeout); - g_bus_unwatch_name (watch); - - /* start portals */ - name_appeared = FALSE; - watch = g_bus_watch_name_on_connection (session_bus, - PORTAL_BUS_NAME, - 0, - name_appeared_cb, - name_disappeared_cb, - &name_appeared, - NULL); - - portal_dir = g_test_build_filename (G_TEST_BUILT, "portals", "test", NULL); - - g_clear_object (&launcher); - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); - g_subprocess_launcher_setenv (launcher, "G_DEBUG", "fatal-criticals", TRUE); - g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", g_test_dbus_get_bus_address (dbus), TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DESKTOP_PORTAL_DIR", portal_dir, TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DATA_HOME", outdir, TRUE); - g_subprocess_launcher_setenv (launcher, "PATH", g_getenv ("PATH"), TRUE); - g_subprocess_launcher_take_stdout_fd (launcher, xdup (STDERR_FILENO)); - - g_clear_pointer (&argv0, g_free); - - if (g_getenv ("XDP_UNINSTALLED") != NULL) - argv0 = g_test_build_filename (G_TEST_BUILT, "..", XDG_DP_BUILDDIR, "xdg-desktop-portal", NULL); - else - argv0 = g_strdup (LIBEXECDIR "/xdg-desktop-portal"); - - argv[0] = argv0; - argv[1] = g_test_verbose () ? "--verbose" : NULL; - argv[2] = NULL; - - g_debug ("launching %s\n", argv0); - - subprocess = g_subprocess_launcher_spawnv (launcher, argv, &error); - g_assert_no_error (error); - g_test_message ("Launched %s with pid %s\n", argv[0], - g_subprocess_get_identifier (subprocess)); - test_procs = g_list_append (test_procs, g_steal_pointer (&subprocess)); - - name_timeout = g_timeout_add (1000 * timeout_mult, timeout_cb, "Failed to launch xdg-desktop-portal"); - - while (!name_appeared) - g_main_context_iteration (NULL, TRUE); - - g_source_remove (name_timeout); - g_bus_unwatch_name (watch); - - permission_store = xdp_dbus_impl_permission_store_proxy_new_sync (session_bus, - 0, - "org.freedesktop.impl.portal.PermissionStore", - "/org/freedesktop/impl/portal/PermissionStore", - NULL, - &error); - g_assert_no_error (error); - - lockdown = xdp_dbus_impl_lockdown_proxy_new_sync (session_bus, - 0, - BACKEND_BUS_NAME, - BACKEND_OBJECT_PATH, - NULL, - &error); - g_assert_no_error (error); - - /* make sure errors are registered */ - portal_errors = XDG_DESKTOP_PORTAL_ERROR; -} - -static void -wait_for_test_procs (void) -{ - GList *l; - - for (l = test_procs; l; l = l->next) - { - GSubprocess *subprocess = G_SUBPROCESS (l->data); - GError *error = NULL; - g_autofree char *identifier = NULL; - - identifier = g_strdup (g_subprocess_get_identifier (subprocess)); - - g_debug ("Terminating and waiting for process %s", identifier); - g_subprocess_send_signal (subprocess, SIGTERM); - - /* This may lead the test to hang, we assume that the test suite or CI - * can handle the case at upper level, without having us async function - * and timeouts */ - g_subprocess_wait (subprocess, NULL, &error); - g_assert_no_error (error); - g_assert_null (g_subprocess_get_identifier (subprocess)); - - if (!g_subprocess_get_if_exited (subprocess)) - { - g_assert_true (g_subprocess_get_if_signaled (subprocess)); - g_assert_cmpint (g_subprocess_get_term_sig (subprocess), ==, SIGTERM); - } - else if (!g_subprocess_get_successful (subprocess)) - { - g_error ("Subprocess %s, exited with exit status %d", identifier, - g_subprocess_get_exit_status (subprocess)); - } - } -} - -static void -global_teardown (void) -{ - GError *error = NULL; - - g_dbus_connection_flush_sync (session_bus, NULL, &error); - g_assert_no_error (error); - - g_dbus_connection_close_sync (session_bus, NULL, &error); - g_assert_no_error (error); - - wait_for_test_procs (); - g_list_free_full (g_steal_pointer (&test_procs), g_object_unref); - - g_object_unref (lockdown); - g_object_unref (permission_store); - - g_object_unref (session_bus); - - g_test_dbus_down (dbus); - - g_object_unref (dbus); -} - -#ifdef HAVE_GEOCLUE -#define check_geoclue(name) -#else -#define check_geoclue(name) \ - if (strcmp (name , "location") == 0) \ - { \ - g_test_skip ("Skipping tests that require geoclue"); \ - return; \ - } -#endif - -/* Just check that the portal is there, and has the - * expected version. This will fail if the backend - * is not found. - */ -#define DEFINE_TEST_EXISTS(pp,PP,version) \ -static void \ -test_##pp##_exists (void) \ -{ \ - g_autoptr(GDBusProxy) proxy = NULL; \ - g_autoptr(GError) error = NULL; \ - g_autofree char *owner = NULL; \ - \ - check_geoclue ( #pp ) \ - \ - proxy = G_DBUS_PROXY (xdp_dbus_##pp##_proxy_new_sync (session_bus, \ - 0, \ - PORTAL_BUS_NAME, \ - PORTAL_OBJECT_PATH, \ - NULL, \ - &error)); \ - g_assert_no_error (error); \ - \ - owner = g_dbus_proxy_get_name_owner (proxy); \ - g_assert_nonnull (owner); \ - \ - g_assert_cmpuint (xdp_dbus_##pp##_get_version (XDP_DBUS_##PP (proxy)), ==, version); \ -} - -DEFINE_TEST_EXISTS(account, ACCOUNT, 1) -DEFINE_TEST_EXISTS(background, BACKGROUND, 2) -DEFINE_TEST_EXISTS(camera, CAMERA, 1) -DEFINE_TEST_EXISTS(email, EMAIL, 4) -DEFINE_TEST_EXISTS(file_chooser, FILE_CHOOSER, 3) -DEFINE_TEST_EXISTS(game_mode, GAME_MODE, 4) -DEFINE_TEST_EXISTS(inhibit, INHIBIT, 3) -DEFINE_TEST_EXISTS(location, LOCATION, 1) -DEFINE_TEST_EXISTS(network_monitor, NETWORK_MONITOR, 3) -DEFINE_TEST_EXISTS(notification, NOTIFICATION, 1) -DEFINE_TEST_EXISTS(open_uri, OPEN_URI, 3) -DEFINE_TEST_EXISTS(print, PRINT, 2) -DEFINE_TEST_EXISTS(proxy_resolver, PROXY_RESOLVER, 1) -DEFINE_TEST_EXISTS(screenshot, SCREENSHOT, 2) -DEFINE_TEST_EXISTS(settings, SETTINGS, 1) -DEFINE_TEST_EXISTS(trash, TRASH, 1) -DEFINE_TEST_EXISTS(wallpaper, WALLPAPER, 1) -DEFINE_TEST_EXISTS(realtime, REALTIME, 1) - -int -main (int argc, char **argv) -{ - int res; - - /* Better leak reporting without gvfs */ - g_setenv ("GIO_USE_VFS", "local", TRUE); - - g_log_writer_default_set_use_stderr (TRUE); - - setlocale (LC_ALL, NULL); - - g_test_init (&argc, &argv, NULL); - - g_test_add_func ("/portal/account/exists", test_account_exists); - g_test_add_func ("/portal/background/exists", test_background_exists); - g_test_add_func ("/portal/camera/exists", test_camera_exists); - g_test_add_func ("/portal/email/exists", test_email_exists); - g_test_add_func ("/portal/filechooser/exists", test_file_chooser_exists); - g_test_add_func ("/portal/gamemode/exists", test_game_mode_exists); - g_test_add_func ("/portal/inhibit/exists", test_inhibit_exists); - g_test_add_func ("/portal/location/exists", test_location_exists); - g_test_add_func ("/portal/networkmonitor/exists", test_network_monitor_exists); - g_test_add_func ("/portal/notification/exists", test_notification_exists); - g_test_add_func ("/portal/openuri/exists", test_open_uri_exists); - g_test_add_func ("/portal/print/exists", test_print_exists); - g_test_add_func ("/portal/proxyresolver/exists", test_proxy_resolver_exists); - g_test_add_func ("/portal/screenshot/exists", test_screenshot_exists); - g_test_add_func ("/portal/settings/exists", test_settings_exists); - g_test_add_func ("/portal/trash/exists", test_trash_exists); - g_test_add_func ("/portal/wallpaper/exists", test_wallpaper_exists); - g_test_add_func ("/portal/realtime/exists", test_realtime_exists); - -#ifdef HAVE_LIBPORTAL - g_test_add_func ("/portal/account/basic", test_account_basic); - g_test_add_func ("/portal/account/delay", test_account_delay); - g_test_add_func ("/portal/account/cancel", test_account_cancel); - g_test_add_func ("/portal/account/close", test_account_close); - g_test_add_func ("/portal/account/reason", test_account_reason); - g_test_add_func ("/portal/account/parallel", test_account_parallel); - - g_test_add_func ("/portal/email/basic", test_email_basic); - g_test_add_func ("/portal/email/delay", test_email_delay); - g_test_add_func ("/portal/email/cancel", test_email_cancel); - g_test_add_func ("/portal/email/close", test_email_close); - g_test_add_func ("/portal/email/address", test_email_address); - g_test_add_func ("/portal/email/punycode_address", test_email_punycode_address); - g_test_add_func ("/portal/email/subject", test_email_subject); - g_test_add_func ("/portal/email/parallel", test_email_parallel); - - g_test_add_func ("/portal/screenshot/basic", test_screenshot_basic); - g_test_add_func ("/portal/screenshot/delay", test_screenshot_delay); - g_test_add_func ("/portal/screenshot/cancel", test_screenshot_cancel); - g_test_add_func ("/portal/screenshot/close", test_screenshot_close); - g_test_add_func ("/portal/screenshot/parallel", test_screenshot_parallel); - - g_test_add_func ("/portal/color/basic", test_color_basic); - g_test_add_func ("/portal/color/delay", test_color_delay); - g_test_add_func ("/portal/color/cancel", test_color_cancel); - g_test_add_func ("/portal/color/close", test_color_close); - g_test_add_func ("/portal/color/parallel", test_color_parallel); - - g_test_add_func ("/portal/trash/file", test_trash_file); - - g_test_add_func ("/portal/openfile/basic", test_open_file_basic); - g_test_add_func ("/portal/openfile/delay", test_open_file_delay); - g_test_add_func ("/portal/openfile/close", test_open_file_close); - g_test_add_func ("/portal/openfile/cancel", test_open_file_cancel); - g_test_add_func ("/portal/openfile/multiple", test_open_file_multiple); - g_test_add_func ("/portal/openfile/filters1", test_open_file_filters1); - g_test_add_func ("/portal/openfile/filters2", test_open_file_filters2); - g_test_add_func ("/portal/openfile/current_filter1", test_open_file_current_filter1); - g_test_add_func ("/portal/openfile/current_filter2", test_open_file_current_filter2); - g_test_add_func ("/portal/openfile/current_filter3", test_open_file_current_filter3); - g_test_add_func ("/portal/openfile/current_filter4", test_open_file_current_filter4); - g_test_add_func ("/portal/openfile/choices1", test_open_file_choices1); - g_test_add_func ("/portal/openfile/choices2", test_open_file_choices2); - g_test_add_func ("/portal/openfile/choices3", test_open_file_choices3); - g_test_add_func ("/portal/openfile/parallel", test_open_file_parallel); - - g_test_add_func ("/portal/savefile/basic", test_save_file_basic); - g_test_add_func ("/portal/savefile/delay", test_save_file_delay); - g_test_add_func ("/portal/savefile/close", test_save_file_close); - g_test_add_func ("/portal/savefile/cancel", test_save_file_cancel); - g_test_add_func ("/portal/savefile/filters", test_save_file_filters); - g_test_add_func ("/portal/savefile/lockdown", test_save_file_lockdown); - g_test_add_func ("/portal/savefile/parallel", test_save_file_parallel); - - g_test_add_func ("/portal/prepareprint/basic", test_prepare_print_basic); - g_test_add_func ("/portal/prepareprint/delay", test_prepare_print_delay); - g_test_add_func ("/portal/prepareprint/close", test_prepare_print_close); - g_test_add_func ("/portal/prepareprint/cancel", test_prepare_print_cancel); - g_test_add_func ("/portal/prepareprint/lockdown", test_prepare_print_lockdown); - g_test_add_func ("/portal/prepareprint/results", test_prepare_print_results); - g_test_add_func ("/portal/prepareprint/parallel", test_prepare_print_parallel); - - g_test_add_func ("/portal/print/basic", test_print_basic); - g_test_add_func ("/portal/print/delay", test_print_delay); - g_test_add_func ("/portal/print/close", test_print_close); - g_test_add_func ("/portal/print/cancel", test_print_cancel); - g_test_add_func ("/portal/print/lockdown", test_print_lockdown); - g_test_add_func ("/portal/print/parallel", test_print_parallel); - - g_test_add_func ("/portal/camera/basic", test_camera_basic); - g_test_add_func ("/portal/camera/delay", test_camera_delay); - g_test_add_func ("/portal/camera/close", test_camera_close); - g_test_add_func ("/portal/camera/cancel", test_camera_cancel); - g_test_add_func ("/portal/camera/lockdown", test_camera_lockdown); - g_test_add_func ("/portal/camera/noaccess1", test_camera_no_access1); - g_test_add_func ("/portal/camera/noaccess2", test_camera_no_access2); - g_test_add_func ("/portal/camera/parallel", test_camera_parallel); - - g_test_add_func ("/portal/inhibit/basic", test_inhibit_basic); - g_test_add_func ("/portal/inhibit/delay", test_inhibit_delay); - g_test_add_func ("/portal/inhibit/close", test_inhibit_close); - g_test_add_func ("/portal/inhibit/cancel", test_inhibit_cancel); - g_test_add_func ("/portal/inhibit/parallel", test_inhibit_parallel); - g_test_add_func ("/portal/inhibit/permissions", test_inhibit_permissions); - g_test_add_func ("/portal/inhibit/monitor", test_inhibit_monitor); - - g_test_add_func ("/portal/openuri/http", test_open_uri_http); - g_test_add_func ("/portal/openuri/http2", test_open_uri_http2); - g_test_add_func ("/portal/openuri/file", test_open_uri_file); - g_test_add_func ("/portal/openuri/delay", test_open_uri_delay); - g_test_add_func ("/portal/openuri/close", test_open_uri_close); - g_test_add_func ("/portal/openuri/cancel", test_open_uri_cancel); - g_test_add_func ("/portal/openuri/lockdown", test_open_uri_lockdown); - g_test_add_func ("/portal/openuri/directory", test_open_directory); - - g_test_add_func ("/portal/wallpaper/basic", test_wallpaper_basic); - g_test_add_func ("/portal/wallpaper/delay", test_wallpaper_delay); - g_test_add_func ("/portal/wallpaper/cancel1", test_wallpaper_cancel1); - g_test_add_func ("/portal/wallpaper/cancel2", test_wallpaper_cancel2); - g_test_add_func ("/portal/wallpaper/permission", test_wallpaper_permission); - - g_test_add_func ("/portal/location/basic", test_location_basic); - g_test_add_func ("/portal/location/accuracy", test_location_accuracy); - - g_test_add_func ("/portal/background/basic1", test_background_basic1); - g_test_add_func ("/portal/background/basic2", test_background_basic2); - g_test_add_func ("/portal/background/commandline", test_background_commandline); - g_test_add_func ("/portal/background/reason", test_background_reason); - - g_test_add_func ("/portal/notification/basic", test_notification_basic); - g_test_add_func ("/portal/notification/buttons", test_notification_buttons); - g_test_add_func ("/portal/notification/bad-arg", test_notification_bad_arg); - g_test_add_func ("/portal/notification/bad-priority", test_notification_bad_priority); - g_test_add_func ("/portal/notification/bad-button", test_notification_bad_button); -#endif - - global_setup (); - - res = g_test_run (); - - sleep (1); - - global_teardown (); - - return res; -} - diff --git a/tests/test-xdp-method-info.c b/tests/test-xdp-method-info.c new file mode 100644 index 0000000..ff0874d --- /dev/null +++ b/tests/test-xdp-method-info.c @@ -0,0 +1,69 @@ +#include "config.h" + +#include + +#include "xdp-method-info.h" + +static void +test_method_info_all (void) +{ + unsigned int i; + unsigned int count = xdp_method_info_get_count (); + const XdpMethodInfo *method_info = xdp_method_info_get_all (); + + g_assert_cmpint (count, >, 100); + g_assert_nonnull (method_info); + + for (i = 0; i < count + 1; i++) + { + if (method_info->interface == NULL) + return; + g_assert_nonnull (method_info->method); + method_info++; + } + + g_assert_not_reached(); +} + +static void +test_method_info_find (void) +{ + const XdpMethodInfo *method_info; + + method_info = xdp_method_info_find ("org.freedesktop.portal.Notification", "AddNotification"); + g_assert_nonnull (method_info); + g_assert_cmpint (method_info->option_arg, ==, -1); + g_assert_false (method_info->uses_request); + + method_info = xdp_method_info_find ("org.freedesktop.portal.Inhibit", "Inhibit"); + g_assert_nonnull (method_info); + g_assert_cmpint (method_info->option_arg, ==, 2); + g_assert_true (method_info->uses_request); + + method_info = xdp_method_info_find ("org.freedesktop.portal.Inhibit", "QueryEndResponse"); + g_assert_nonnull (method_info); + g_assert_cmpint (method_info->option_arg, ==, -1); + g_assert_false (method_info->uses_request); + + /* Prefix is required */ + method_info = xdp_method_info_find ("Inhibit", "QueryEndResponse"); + g_assert_null (method_info); + + method_info = xdp_method_info_find ("DoesNotExist", "DoesNotExist"); + g_assert_null (method_info); + + method_info = xdp_method_info_find ("DoesNotExist", "DoesNotExist"); + g_assert_null (method_info); + + method_info = xdp_method_info_find ("org.freedesktop.portal.Inhibit", "DoesNotExist"); + g_assert_null (method_info); + +} + +int main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/method-info/all", test_method_info_all); + g_test_add_func ("/method-info/find", test_method_info_find); + return g_test_run (); +} diff --git a/tests/test-xdp-utils.c b/tests/test-xdp-utils.c index 531b240..cc50e6c 100644 --- a/tests/test-xdp-utils.c +++ b/tests/test-xdp-utils.c @@ -2,8 +2,14 @@ #include +#include "xdp-app-info-private.h" +#include "xdp-app-info-snap-private.h" +#include "xdp-app-info-host-private.h" #include "xdp-utils.h" +#define snap_parse_cgroup _xdp_app_info_snap_parse_cgroup_file +#define host_parse_app_id _xdp_app_info_host_parse_app_id_from_unit_name + static void test_parse_cgroup_unified (void) { @@ -14,7 +20,7 @@ test_parse_cgroup_unified (void) f = fmemopen(data, sizeof(data), "r"); - res = _xdp_parse_cgroup_file (f, &is_snap); + res = snap_parse_cgroup (f, &is_snap); g_assert_cmpint (res, ==, 0); g_assert_true (is_snap); fclose(f); @@ -43,7 +49,7 @@ test_parse_cgroup_freezer (void) f = fmemopen(data, sizeof(data), "r"); - res = _xdp_parse_cgroup_file (f, &is_snap); + res = snap_parse_cgroup (f, &is_snap); g_assert_cmpint (res, ==, 0); g_assert_true (is_snap); fclose(f); @@ -59,7 +65,7 @@ test_parse_cgroup_systemd (void) f = fmemopen(data, sizeof(data), "r"); - res = _xdp_parse_cgroup_file (f, &is_snap); + res = snap_parse_cgroup (f, &is_snap); g_assert_cmpint (res, ==, 0); g_assert_true (is_snap); fclose(f); @@ -89,7 +95,7 @@ test_parse_cgroup_not_snap (void) f = fmemopen(data, sizeof(data), "r"); - res = _xdp_parse_cgroup_file (f, &is_snap); + res = snap_parse_cgroup (f, &is_snap); g_assert_cmpint (res, ==, 0); g_assert_false (is_snap); fclose(f); @@ -133,52 +139,52 @@ test_app_id_via_systemd_unit (void) { g_autofree char *app_id = NULL; - app_id = _xdp_parse_app_id_from_unit_name ("app-not-a-well-formed-unit-name"); + app_id = host_parse_app_id ("app-not-a-well-formed-unit-name"); g_assert_cmpstr (app_id, ==, ""); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-gnome-org.gnome.Evolution\\x2dalarm\\x2dnotify-2437.scope"); + app_id = host_parse_app_id ("app-gnome-org.gnome.Evolution\\x2dalarm\\x2dnotify-2437.scope"); /* Note, this is not Evolution's app ID, because the scope is for a background service */ g_assert_cmpstr (app_id, ==, "org.gnome.Evolution-alarm-notify"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-gnome-org.gnome.Epiphany-182352.scope"); + app_id = host_parse_app_id ("app-gnome-org.gnome.Epiphany-182352.scope"); g_assert_cmpstr (app_id, ==, "org.gnome.Epiphany"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-glib-spice\\x2dvdagent-1839.scope"); + app_id = host_parse_app_id ("app-glib-spice\\x2dvdagent-1839.scope"); g_assert_cmpstr (app_id, ==, "spice-vdagent"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-KDE-org.kde.okular@12345.service"); + app_id = host_parse_app_id ("app-KDE-org.kde.okular@12345.service"); g_assert_cmpstr (app_id, ==, "org.kde.okular"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-firefox.service"); + app_id = host_parse_app_id ("app-firefox.service"); g_assert_cmpstr (app_id, ==, "firefox"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-org.kde.amarok.service"); + app_id = host_parse_app_id ("app-org.kde.amarok.service"); g_assert_cmpstr (app_id, ==, "org.kde.amarok"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-gnome-org.gnome.SettingsDaemon.DiskUtilityNotify-autostart.service"); + app_id = host_parse_app_id ("app-gnome-org.gnome.SettingsDaemon.DiskUtilityNotify-autostart.service"); g_assert_cmpstr (app_id, ==, "org.gnome.SettingsDaemon.DiskUtilityNotify"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-gnome-org.gnome.Terminal-92502.slice"); + app_id = host_parse_app_id ("app-gnome-org.gnome.Terminal-92502.slice"); g_assert_cmpstr (app_id, ==, "org.gnome.Terminal"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-com.obsproject.Studio-d70acc38b5154a3a8b4a60accc4b15f4.scope"); + app_id = host_parse_app_id ("app-com.obsproject.Studio-d70acc38b5154a3a8b4a60accc4b15f4.scope"); g_assert_cmpstr (app_id, ==, "com.obsproject.Studio"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-firefox-jcfppqx.scope"); + app_id = host_parse_app_id ("app-firefox-jcfppqx.scope"); g_assert_cmpstr (app_id, ==, "firefox"); g_clear_pointer (&app_id, g_free); - app_id = _xdp_parse_app_id_from_unit_name ("app-gnome-firefox.service"); + app_id = host_parse_app_id ("app-gnome-firefox.service"); g_assert_cmpstr (app_id, ==, "firefox"); g_clear_pointer (&app_id, g_free); } diff --git a/tests/test_account.py b/tests/test_account.py new file mode 100644 index 0000000..1272d89 --- /dev/null +++ b/tests/test_account.py @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import pytest + + +ACCOUNT_DATA = { + "id": "test", + "name": "Test Name", + "image": "file:///image.png", +} + + +@pytest.fixture +def required_templates(): + return { + "account": { + "results": ACCOUNT_DATA, + }, + } + + +class TestAccount: + def set_permission(self, dbus_con, app_id, permission): + perm_store_intf = xdp.get_permission_store_iface(dbus_con) + perm_store_intf.SetPermission( + "wallpaper", + True, + "wallpaper", + app_id, + [permission], + ) + + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Account", 1) + + def test_basic1(self, portals, dbus_con, app_id): + account_intf = xdp.get_portal_iface(dbus_con, "Account") + mock_intf = xdp.get_mock_iface(dbus_con) + + reason = "reason" + + request = xdp.Request(dbus_con, account_intf) + options = { + "reason": reason, + } + response = request.call( + "GetUserInformation", + window="", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["id"] == ACCOUNT_DATA["id"] + assert response.results["name"] == ACCOUNT_DATA["name"] + assert response.results["image"] == ACCOUNT_DATA["image"] + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("GetUserInformation") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # window + assert args[3]["reason"] == reason + + def test_reason(self, portals, dbus_con): + account_intf = xdp.get_portal_iface(dbus_con, "Account") + mock_intf = xdp.get_mock_iface(dbus_con) + + reason = """This reason is unreasonably long, it stretches over + more than twohundredfiftysix characters, which is really quite + long. Excessively so. The portal frontend will silently drop + reasons of this magnitude. If you can't express your reasons + concisely, you probably have no good reason in the first place + and are just waffling around.""" + + assert len(reason) - 1 > 256 + + request = xdp.Request(dbus_con, account_intf) + options = { + "reason": reason, + } + response = request.call( + "GetUserInformation", + window="", + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("GetUserInformation") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert "reason" not in args[3] + + @pytest.mark.parametrize("template_params", ({"account": {"expect-close": True}},)) + def test_close(self, portals, dbus_con): + account_intf = xdp.get_portal_iface(dbus_con, "Account") + + reason = "reason" + + request = xdp.Request(dbus_con, account_intf) + request.schedule_close(1000) + options = { + "reason": reason, + } + request.call( + "GetUserInformation", + window="", + options=options, + ) + + # Only true if the impl.Request was closed too + assert request.closed diff --git a/tests/test_background.py b/tests/test_background.py new file mode 100644 index 0000000..7efea88 --- /dev/null +++ b/tests/test_background.py @@ -0,0 +1,167 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import dbus +import pytest +import os +from pathlib import Path +from gi.repository import GLib + + +@pytest.fixture +def required_templates(): + return {"background": {}} + + +class TestBackground: + def get_autostart_path(self, app_id): + return Path(os.environ["XDG_CONFIG_HOME"]) / "autostart" / f"{app_id}.desktop" + + def get_autostart_keyfile(self, app_id): + keyfile = GLib.KeyFile.new() + + desktop_file_path = self.get_autostart_path(app_id) + with open(str(desktop_file_path.absolute())) as desktop_file: + desktop_file_contents = desktop_file.read() + + assert keyfile.load_from_data( + desktop_file_contents, + len(desktop_file_contents), + GLib.KeyFileFlags.NONE, + ) + + return keyfile + + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Background", 2) + + def test_request_background(self, portals, dbus_con, app_id): + background_intf = xdp.get_portal_iface(dbus_con, "Background") + desktop_file = self.get_autostart_path(app_id) + + reason = "Testing portals" + + request = xdp.Request(dbus_con, background_intf) + options = { + "reason": reason, + } + response = request.call( + "RequestBackground", + parent_window="", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["background"] + assert not response.results["autostart"] + + assert not desktop_file.exists() + + def test_autostart_desktopfile(self, portals, dbus_con, app_id): + background_intf = xdp.get_portal_iface(dbus_con, "Background") + + reason = "Testing portals" + autostart = True + commandline = ["/bin/true", "test"] + dbus_activatable = True + + request = xdp.Request(dbus_con, background_intf) + options = { + "reason": reason, + "autostart": autostart, + "commandline": commandline, + "dbus-activatable": dbus_activatable, + } + response = request.call( + "RequestBackground", + parent_window="", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["background"] + assert response.results["autostart"] + + keyfile = self.get_autostart_keyfile(app_id) + assert keyfile.get_string("Desktop Entry", "Type") == "Application" + assert keyfile.get_string("Desktop Entry", "Name") == app_id + assert keyfile.get_string("Desktop Entry", "X-XDP-Autostart") == app_id + assert keyfile.get_string("Desktop Entry", "Exec") == "/bin/true test" + assert keyfile.get_boolean("Desktop Entry", "DBusActivatable") + + def test_autostart_disable(self, portals, dbus_con, app_id): + background_intf = xdp.get_portal_iface(dbus_con, "Background") + desktop_file = self.get_autostart_path(app_id) + + reason = "Testing portals" + autostart = True + + request = xdp.Request(dbus_con, background_intf) + options = { + "reason": reason, + "autostart": autostart, + } + response = request.call( + "RequestBackground", + parent_window="", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["background"] + assert response.results["autostart"] + + assert desktop_file.exists() + + request = xdp.Request(dbus_con, background_intf) + options = { + "reason": reason, + } + response = request.call( + "RequestBackground", + parent_window="", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["background"] + assert not response.results["autostart"] + + assert not desktop_file.exists() + + def test_long_reason(self, portals, dbus_con, app_id): + background_intf = xdp.get_portal_iface(dbus_con, "Background") + + reason = ( + "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + + "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + + "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + ) + autostart = True + commandline = ["/bin/true", "test"] + dbus_activatable = True + + request = xdp.Request(dbus_con, background_intf) + options = { + "reason": reason, + "autostart": autostart, + "commandline": commandline, + "dbus-activatable": dbus_activatable, + } + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "RequestBackground", + parent_window="", + options=options, + ) + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) diff --git a/tests/test_camera.py b/tests/test_camera.py new file mode 100644 index 0000000..bad27f2 --- /dev/null +++ b/tests/test_camera.py @@ -0,0 +1,139 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import dbus +import pytest + + +@pytest.fixture +def required_templates(): + return { + "access": {}, + "lockdown": {}, + } + + +@pytest.fixture +def app_id(): + # x-d-p currently defaults to the empty app id for the camera portal for + # host XdpAppInfos (which the XdpAppInfoTest is). So use the empty app_id + # for now. + return "" + + +class TestCamera: + def set_permissions(self, dbus_con, appid, permissions): + perm_store_intf = xdp.get_permission_store_iface(dbus_con) + perm_store_intf.SetPermission( + "devices", + True, + "camera", + appid, + permissions, + ) + + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Camera", 1) + + def test_access(self, portals, dbus_con, app_id): + camera_intf = xdp.get_portal_iface(dbus_con, "Camera") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, camera_intf) + response = request.call( + "AccessCamera", + options={}, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("AccessDialog") + assert len(method_calls) == 1 + _, args = method_calls[-1] + assert args[1] == app_id + + @pytest.mark.parametrize("template_params", ({"access": {"response": 1}},)) + def test_access_cancel(self, portals, dbus_con, app_id): + camera_intf = xdp.get_portal_iface(dbus_con, "Camera") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, camera_intf) + response = request.call( + "AccessCamera", + options={}, + ) + + assert response + assert response.response == 1 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("AccessDialog") + assert len(method_calls) == 1 + _, args = method_calls[-1] + assert args[1] == app_id + + @pytest.mark.parametrize("template_params", ({"access": {"expect-close": True}},)) + def test_access_close(self, portals, dbus_con, app_id): + camera_intf = xdp.get_portal_iface(dbus_con, "Camera") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, camera_intf) + request.schedule_close(1000) + request.call( + "AccessCamera", + options={}, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("AccessDialog") + assert len(method_calls) == 1 + _, args = method_calls[-1] + assert args[1] == app_id + + @pytest.mark.parametrize( + "template_params", ({"lockdown": {"disable-camera": True}},) + ) + def test_access_lockdown(self, portals, dbus_con, app_id): + camera_intf = xdp.get_portal_iface(dbus_con, "Camera") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, camera_intf) + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "AccessCamera", + options={}, + ) + assert ( + excinfo.value.get_dbus_name() == "org.freedesktop.portal.Error.NotAllowed" + ) + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("AccessDialog") + assert len(method_calls) == 0 + + def test_access_denied(self, portals, dbus_con, app_id): + camera_intf = xdp.get_portal_iface(dbus_con, "Camera") + mock_intf = xdp.get_mock_iface(dbus_con) + + self.set_permissions(dbus_con, app_id, ["no"]) + + request = xdp.Request(dbus_con, camera_intf) + response = request.call( + "AccessCamera", + options={}, + ) + + assert response + assert response.response == 1 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("AccessDialog") + assert len(method_calls) == 0 diff --git a/tests/test_clipboard.py b/tests/test_clipboard.py index e2db531..2ca5792 100644 --- a/tests/test_clipboard.py +++ b/tests/test_clipboard.py @@ -2,79 +2,79 @@ # # This file is formatted with Python Black -from tests import PortalMock, Session +import tests as xdp + import dbus import pytest import os @pytest.fixture -def portal_name(): - return "Clipboard" +def required_templates(): + return { + "Clipboard": {}, + "RemoteDesktop": {"force-clipboard-enabled": True}, + } class TestClipboard: - def test_version(self, portal_mock): - portal_mock.check_version(1) + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Clipboard", 1) - def start_session(self, pmock, params={}): - pmock.start_impl_portal(params=params) - pmock.add_template(portal="RemoteDesktop", params=params) - pmock.start_xdp() + def start_session(self, dbus_con): + clipboard_intf = xdp.get_portal_iface(dbus_con, "Clipboard") + remotedesktop_intf = xdp.get_portal_iface(dbus_con, "RemoteDesktop") - create_session_request = pmock.create_request("RemoteDesktop") + create_session_request = xdp.Request(dbus_con, remotedesktop_intf) create_session_response = create_session_request.call( "CreateSession", options={"session_handle_token": "1234"} ) + assert create_session_response assert create_session_response.response == 0 assert str(create_session_response.results["session_handle"]) - session = Session.from_response(pmock.dbus_con, create_session_response) + session = xdp.Session.from_response(dbus_con, create_session_response) - clipboard_interface = pmock.get_dbus_interface() - clipboard_interface.RequestClipboard(session.handle, {}) + clipboard_intf.RequestClipboard(session.handle, {}) - start_session_request = pmock.create_request("RemoteDesktop") + start_session_request = xdp.Request(dbus_con, remotedesktop_intf) start_session_response = start_session_request.call( "Start", session_handle=session.handle, parent_window="", options={} ) + assert start_session_response assert start_session_response.response == 0 return (session, start_session_response.results.get("clipboard_enabled")) - def test_request_clipboard_and_start_session(self, session_bus): - pmock = PortalMock(session_bus, "Clipboard") - params = {"force-clipboard-enabled": True} - _, clipboard_enabled = self.start_session(pmock, params) + def test_request_clipboard_and_start_session(self, portals, dbus_con): + _, clipboard_enabled = self.start_session(dbus_con) assert clipboard_enabled - def test_clipboard_checks_clipboard_enabled(self, session_bus): - pmock = PortalMock(session_bus, "Clipboard") - session, clipboard_enabled = self.start_session(pmock) - clipboard_interface = pmock.get_dbus_interface() + @pytest.mark.parametrize( + "template_params", ({"RemoteDesktop": {"force-clipboard-enabled": False}},) + ) + def test_checks_clipboard_enabled(self, portals, dbus_con): + clipboard_intf = xdp.get_portal_iface(dbus_con, "Clipboard") + session, clipboard_enabled = self.start_session(dbus_con) assert not clipboard_enabled with pytest.raises(dbus.exceptions.DBusException): - clipboard_interface.SetSelection(session.handle, {}) + clipboard_intf.SetSelection(session.handle, {}) - def test_clipboard_set_selection(self, session_bus): - pmock = PortalMock(session_bus, "Clipboard") - params = {"force-clipboard-enabled": True} - session, _ = self.start_session(pmock, params) - clipboard_interface = pmock.get_dbus_interface() + def test_set_selection(self, portals, dbus_con): + clipboard_intf = xdp.get_portal_iface(dbus_con, "Clipboard") + session, _ = self.start_session(dbus_con) - clipboard_interface.SetSelection(session.handle, {}) + clipboard_intf.SetSelection(session.handle, {}) - def test_clipboard_selection_write(self, session_bus): - pmock = PortalMock(session_bus, "Clipboard") - params = {"force-clipboard-enabled": True} - session, _ = self.start_session(pmock, params) - clipboard_interface = pmock.get_dbus_interface() + def test_selection_write(self, portals, dbus_con): + clipboard_intf = xdp.get_portal_iface(dbus_con, "Clipboard") + session, _ = self.start_session(dbus_con) - fd_object: dbus.types.UnixFd = clipboard_interface.SelectionWrite( + fd_object: dbus.types.UnixFd = clipboard_intf.SelectionWrite( session.handle, 1234 ) assert fd_object @@ -85,15 +85,13 @@ def test_clipboard_selection_write(self, session_bus): bytes_written = os.write(fd, b"Clipboard") assert bytes_written > 0 - clipboard_interface.SelectionWriteDone(session.handle, 1234, True) + clipboard_intf.SelectionWriteDone(session.handle, 1234, True) - def test_clipboard_selection_read(self, session_bus): - pmock = PortalMock(session_bus, "Clipboard") - params = {"force-clipboard-enabled": True} - session, _ = self.start_session(pmock, params) - clipboard_interface = pmock.get_dbus_interface() + def test_selection_read(self, portals, dbus_con): + clipboard_intf = xdp.get_portal_iface(dbus_con, "Clipboard") + session, _ = self.start_session(dbus_con) - fd_object: dbus.types.UnixFd = clipboard_interface.SelectionRead( + fd_object: dbus.types.UnixFd = clipboard_intf.SelectionRead( session.handle, "mimetype" ) assert fd_object @@ -101,5 +99,5 @@ def test_clipboard_selection_read(self, session_bus): fd = fd_object.take() assert fd - clipboard = os.read(fd, 1000) - assert str(clipboard) + clipboard_contents = os.read(fd, 1000) + assert str(clipboard_contents) diff --git a/tests/test-document-fuse.py b/tests/test_document_fuse.py old mode 100755 new mode 100644 similarity index 89% rename from tests/test-document-fuse.py rename to tests/test_document_fuse.py index e667aa9..1791cd6 --- a/tests/test-document-fuse.py +++ b/tests/test_document_fuse.py @@ -1,78 +1,49 @@ -#!/usr/bin/env python3 - -# Copyright © 2020 Red Hat, Inc -# Copyright © 2023 GNOME Foundation Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. +# SPDX-License-Identifier: LGPL-2.1-or-later # -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . -# -# Authors: -# Alexander Larsson -# Hubert Figuière +# This file is formatted with Python Black + +import tests as xdp -import argparse +import pytest import errno import os import random import stat import sys - +import multiprocessing as mp +import traceback +from collections import defaultdict from gi.repository import Gio, GLib +@pytest.fixture +def app_id(): + return None + + def filename_to_ay(filename): return list(filename.encode("utf-8")) + [0] -running_count = {} - app_prefix = "org.test." dir_prefix = "dir" ensure_no_remaining = True -parser = argparse.ArgumentParser() -parser.add_argument("--verbose", "-v", action="count") -parser.add_argument("--iterations", type=int, default=3) -parser.add_argument("--prefix") -args = parser.parse_args(sys.argv[1:]) - -if args.prefix: - app_prefix = app_prefix + args.prefix + "." - dir_prefix = dir_prefix + "-" + args.prefix + "-" - ensure_no_remaining = False +running_count: defaultdict[str, int] = defaultdict(int) def log(str): - if args.prefix: - print("%s: %s" % (args.prefix, str), file=sys.stderr) - else: - print(str, file=sys.stderr) + print(str, file=sys.stderr) def logv(str): - if args.verbose: - log(str) + log(str) -def get_a_count(counter): +def get_a_count(counter: str): global running_count - if counter in running_count: - count = running_count[counter] - count = count + 1 - running_count[counter] = count - return count - running_count[counter] = 1 - return 1 + running_count[counter] += 1 + return running_count[counter] def setFileContent(path, content): @@ -101,7 +72,6 @@ def appendFdContent(fd, content): os.write(fd, bytes(content, "utf-8")) -TEST_DATA_DIR = os.environ["TEST_DATA_DIR"] DOCUMENT_ADD_FLAGS_REUSE_EXISTING = 1 << 0 DOCUMENT_ADD_FLAGS_PERSISTENT = 1 << 1 DOCUMENT_ADD_FLAGS_AS_NEEDED_BY_APP = 1 << 2 @@ -109,67 +79,35 @@ def appendFdContent(fd, content): def assertRaises(exc_type, func, *args, **kwargs): - raised_exc = None - try: + with pytest.raises(exc_type): func(*args, **kwargs) - except: - raised_exc = sys.exc_info()[0] - - if not raised_exc: - raise AssertionError("{0} was not raised".format(exc_type.__name__)) - if raised_exc != exc_type: - raise AssertionError( - "Wrong assertion type {0} was raised instead of {1}".format( - raised_exc.__name__, exc_type.__name__ - ) - ) def assertRaisesErrno(error_nr, func, *args, **kwargs): - raised_exc = None - raised_exc_value = None - try: + with pytest.raises(OSError) as excinfo: func(*args, **kwargs) - except: - raised_exc = sys.exc_info()[0] - raised_exc_value = sys.exc_info()[1] - - if not raised_exc: - raise AssertionError("No assertion was raised") - if raised_exc != OSError: - raise AssertionError("OSError was not raised") - if raised_exc_value.errno != error_nr: + + if excinfo.value.errno != error_nr: raise AssertionError( "Wrong errno {0} was raised instead of {1}".format( - raised_exc_value.errno, error_nr + excinfo.value.errno, error_nr ) ) def assertRaisesGError(message, code, func, *args, **kwargs): - raised_exc = None - raised_exc_value = None - try: + with pytest.raises(GLib.GError) as excinfo: func(*args, **kwargs) - except: - raised_exc = sys.exc_info()[0] - raised_exc_value = sys.exc_info()[1] - - if not raised_exc: - raise AssertionError("No assertion was raised") - if raised_exc != GLib.GError: - raise AssertionError("GError was not raised") - if not raised_exc_value.message.startswith(message): + + if not excinfo.value.message.startswith(message): raise AssertionError( "Wrong message {0} doesn't start with {1}".format( - raised_exc_value.message, message + excinfo.value.message, message ) ) - if raised_exc_value.code != code: + if excinfo.value.code != code: raise AssertionError( - "Wrong code {0} was raised instead of {1}".format( - raised_exc_value.code, code - ) + "Wrong code {0} was raised instead of {1}".format(excinfo.value.code, code) ) @@ -203,7 +141,7 @@ def assertFileExist(path): info = os.lstat(path) if info.st_mode & stat.S_IFREG != stat.S_IFREG: raise AssertionError("File {} is not a regular file".format(path)) - except: + except Exception: raise AssertionError("File {} doesn't exist".format(path)) @@ -212,7 +150,7 @@ def assertDirExist(path): info = os.lstat(path) if info.st_mode & stat.S_IFDIR != stat.S_IFDIR: raise AssertionError("File {} is not a directory file".format(path)) - except: + except Exception: raise AssertionError("File {} doesn't exist".format(path)) @@ -228,7 +166,7 @@ def assertSymlink(path, expected_target): path, target, expected_target ) ) - except: + except Exception: raise AssertionError("Symlink {} doesn't exist".format(path)) @@ -237,7 +175,7 @@ def assertFileNotExist(path): os.lstat(path) except FileNotFoundError: return - except: + except Exception: raise AssertionError( "Got wrong execption {} for {}, expected FileNotFoundError".format( sys.exc_info()[0], path @@ -404,7 +342,7 @@ def add_named(self, path, reuse_existing=True): try: with open(path) as f: content = f.read() - except: + except Exception: content = None doc = Doc(self, doc_id, path, content) self.docs[doc.id] = doc @@ -476,6 +414,7 @@ def by_app_path(self): def app_path(self, app_id): return self.mountpoint + "/by-app/" + app_id + class FileTransferPortal(DocPortal): def __init__(self): super().__init__() @@ -490,7 +429,9 @@ def __init__(self): ) def start_transfer(self): - res = self.ft_proxy.call_sync("StartTransfer", GLib.Variant("(a{sv})", ([None])), 0, -1, None) + res = self.ft_proxy.call_sync( + "StartTransfer", GLib.Variant("(a{sv})", ([None])), 0, -1, None + ) return res[0] def add_files(self, key, files): @@ -532,6 +473,7 @@ def stop_transfer(self, key): ) return res + def check_virtual_stat(info, writable=False): assert info.st_uid == os.getuid() assert info.st_gid == os.getgid() @@ -1004,7 +946,7 @@ def create_app_by_lookup(portal): def ensure_real_dir(create_hidden_file=True): count = get_a_count("doc") - dir = TEST_DATA_DIR + "/" + dir_prefix + str(count) + dir = os.environ["TMPDIR"] + "/" + dir_prefix + str(count) os.makedirs(dir) if create_hidden_file: setFileContent(dir + "/cant-see-this-file", "s3krit") @@ -1127,6 +1069,7 @@ def add_an_app(portal, num_docs): doc.apps.append(write_app) logv("granted acces to %s and %s for %s" % (read_app, write_app, ids)) + def file_transfer_portal_test(): log("File transfer tests") ft_portal = FileTransferPortal() @@ -1160,24 +1103,58 @@ def file_transfer_portal_test(): # Test that an invalid key is rejected key = ft_portal.start_transfer() assert key != "1234" - assertRaisesGError("GDBus.Error:org.freedesktop.DBus.Error.AccessDenied", 9, ft_portal.add_files, "1234", [file1, file2]) + assertRaisesGError( + "GDBus.Error:org.freedesktop.DBus.Error.AccessDenied", + 9, + ft_portal.add_files, + "1234", + [file1, file2], + ) # Test stop transfer key = ft_portal.start_transfer() ft_portal.add_files(key, [file1, file2]) ft_portal.stop_transfer(key) - assertRaisesGError("GDBus.Error:org.freedesktop.DBus.Error.AccessDenied", 9, ft_portal.retrieve_files, key) - assertRaisesGError("GDBus.Error:org.freedesktop.DBus.Error.AccessDenied", 9, ft_portal.add_files, key, [file1, file2]) + assertRaisesGError( + "GDBus.Error:org.freedesktop.DBus.Error.AccessDenied", + 9, + ft_portal.retrieve_files, + key, + ) + assertRaisesGError( + "GDBus.Error:org.freedesktop.DBus.Error.AccessDenied", + 9, + ft_portal.add_files, + key, + [file1, file2], + ) # Test that we can't reuse an old key new_key = ft_portal.start_transfer() - assertRaisesGError("GDBus.Error:org.freedesktop.DBus.Error.AccessDenied", 9, ft_portal.add_files, key, [file1, file2]) + assertRaisesGError( + "GDBus.Error:org.freedesktop.DBus.Error.AccessDenied", + 9, + ft_portal.add_files, + key, + [file1, file2], + ) res = ft_portal.add_files(new_key, [file1, file2]) log("filetransfer key ok") log("File transfer tests ok") -try: + +def run_test(iterations, prefix=None, do_ensure_no_remaining=True): + global app_prefix + global dir_prefix + global ensure_no_remaining + + if prefix: + app_prefix = app_prefix + prefix + "." + dir_prefix = dir_prefix + "-" + prefix + "-" + + ensure_no_remaining = do_ensure_no_remaining + log("Connecting to portal") doc_portal = DocPortal() @@ -1210,7 +1187,7 @@ def file_transfer_portal_test(): add_an_app(doc_portal, 6) verify_fs_layout(doc_portal) - for i in range(args.iterations): + for i in range(iterations): log("Checking permissions, pass %d" % (i + 1)) check_perms(doc_portal) verify_fs_layout(doc_portal) @@ -1218,7 +1195,65 @@ def file_transfer_portal_test(): log("fuse tests ok") file_transfer_portal_test() - sys.exit(0) -except Exception as e: - log("fuse tests failed: %s" % e) - sys.exit(1) + +class Process(mp.Process): + def __init__(self, *args, **kwargs): + mp.Process.__init__(self, *args, **kwargs) + self._pconn, self._cconn = mp.Pipe() + self._exception = None + + def run(self): + try: + mp.Process.run(self) + self._cconn.send(None) + except Exception as e: + tb = traceback.format_exc() + self._cconn.send((e, tb)) + + @property + def exception(self): + if self._pconn.poll(): + self._exception = self._pconn.recv() + return self._exception + + +class TestDocumentFuse: + def parallel(self, test_function, parallel_tests, parallel_iterations): + procs = [] + for i in range(parallel_tests): + p = Process( + target=test_function, args=(parallel_iterations, f"c{i}", False) + ) + p.start() + procs.append(p) + + for p in procs: + p.join() + + if p.exception: + error, traceback = p.exception + raise error + + def test_single_thread(self, portals, xdg_document_portal, dbus_con): + run_test(3) + + def test_multi_thread(self, portals, xdg_document_portal, dbus_con): + if xdp.run_long_tests(): + return self.parallel(run_test, 20, 10) + if xdp.is_in_ci(): + return self.parallel(run_test, 5, 3) + self.parallel(run_test, 10, 5) + + +# Running +# ./tests/run-test.sh -n 0 tests/test_document_fuse.py::TestDocumentFuse::test_multi_thread +# works fine, but with +# ./tests/run-test.sh -n 0 tests/test_document_fuse.py::TestDocumentFuse +# the `test_multi_thread` test is failing. +# For now, let's skip the test and turn it on again when we have fixed it. +pytest.skip("Test has a race condition which can make it fail", allow_module_level=True) + +try: + xdp.ensure_fuse_supported() +except xdp.FuseNotSupportedException as e: + pytest.skip(f"No fuse support: {e}", allow_module_level=True) diff --git a/tests/test_documents.py b/tests/test_documents.py new file mode 100644 index 0000000..01a6df5 --- /dev/null +++ b/tests/test_documents.py @@ -0,0 +1,421 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import pytest +import dbus +from pathlib import Path +import os +from gi.repository import GLib, Gio + + +EXPORT_FILES_FLAG_EXPORT_DIR = 8 + + +def path_from_null_term_bytes(bytes): + path_bytes, rest = bytes.split(b"\x00") + assert rest == b"" + return Path(os.fsdecode(path_bytes)) + + +def get_mountpoint(documents_intf): + mountpoint = documents_intf.GetMountPoint(byte_arrays=True) + mountpoint = path_from_null_term_bytes(mountpoint) + assert mountpoint.exists() + return mountpoint + + +def write_bytes_atomic(file_path, bytes): + GLib.file_set_contents(file_path.absolute().as_posix(), bytes) + + +def write_bytes_trunc(file_path, bytes): + try: + fd = os.open( + file_path.absolute().as_posix(), os.O_RDWR | os.O_TRUNC | os.O_CREAT + ) + os.write(fd, bytes) + finally: + os.close(fd) + + +def get_host_path_attr(path): + xattr = "xattr::document-portal.host-path" + file = Gio.file_new_for_path(path.absolute().as_posix()) + info = file.query_info(xattr, Gio.FileQueryInfoFlags.NONE) + host_path = info.get_attribute_as_string(xattr) + if not host_path: + return None + return Path(os.fsdecode(host_path)) + + +def export_file(documents_intf, file_path, unique=False): + assert file_path.exists() + + with open(file_path.absolute().as_posix(), "r") as file: + doc_id = documents_intf.Add(file.fileno(), not unique, False) + assert doc_id + + return doc_id + + +def export_file_named(documents_intf, folder_path, name, unique=False): + assert folder_path.exists() + + # bytestring convention is zero terminated + name_nt = os.fsencode(name) + b"\x00" + + try: + fd = os.open(folder_path.absolute().as_posix(), os.O_PATH | os.O_CLOEXEC) + doc_id = documents_intf.AddNamed(fd, name_nt, not unique, False) + assert doc_id + finally: + os.close(fd) + + return doc_id + + +def export_files(documents_intf, file_paths, perms, flags=0, app_id=""): + fds = [] + try: + for file_path in file_paths: + fds.append( + os.open(file_path.absolute().as_posix(), os.O_PATH | os.O_CLOEXEC) + ) + + result = documents_intf.AddFull( + fds, + flags, + app_id, + perms, + byte_arrays=True, + ) + finally: + for fd in fds: + os.close(fd) + + assert result + return result + + +class TestDocuments: + def test_version(self, xdg_document_portal, dbus_con): + documents = dbus_con.get_object( + "org.freedesktop.portal.Documents", + "/org/freedesktop/portal/documents", + ) + + properties_intf = dbus.Interface( + documents, + "org.freedesktop.DBus.Properties", + ) + portal_version = properties_intf.Get( + "org.freedesktop.portal.Documents", + "version", + ) + assert int(portal_version) == 5 + + def test_mount_point(self, xdg_document_portal, dbus_con): + documents_intf = xdp.get_document_portal_iface(dbus_con) + get_mountpoint(documents_intf) + + def test_create_doc(self, xdg_document_portal, dbus_con): + documents_intf = xdp.get_document_portal_iface(dbus_con) + mountpoint = get_mountpoint(documents_intf) + + content = b"content" + file_name = "a-file" + + file_path = Path(os.environ["TMPDIR"]) / file_name + write_bytes_atomic(file_path, content) + doc_id = export_file(documents_intf, file_path) + + doc_path = mountpoint / doc_id + doc_app1_path = mountpoint / "by-app" / "com.test.App1" / doc_id + doc_app2_path = mountpoint / "by-app" / "com.test.App2" / doc_id + + # Make sure it got exported + assert (doc_path / file_name).read_bytes() == content + + assert not (doc_path / "another-file").exists() + assert not (mountpoint / "anotherid" / file_name).exists() + + # Make sure it is not viewable by apps + assert not doc_app1_path.exists() + assert not doc_app2_path.exists() + + # Create a tmp file in same dir, ensure it works and can't be seen by other apps + write_bytes_atomic(doc_path / "tmp1", b"tmpdata1") + assert (doc_path / "tmp1").read_bytes() == b"tmpdata1" + assert not (doc_app1_path / "tmp1").exists() + + # Ensure App 1 and only it can see the document and tmpfile + documents_intf.GrantPermissions(doc_id, "com.test.App1", ["read"]) + assert (doc_app1_path / file_name).read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + # Make sure App 1 can't create a tmpfile + assert not (doc_app1_path / "tmp2").exists() + with pytest.raises(PermissionError): + (doc_app1_path / "tmp2").write_bytes(b"tmpdata1") + assert not (doc_app1_path / "tmp2").exists() + assert not (doc_path / "tmp2").exists() + + # Update the document contents, ensure this is propagated + content = b"content2" + write_bytes_atomic(doc_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert file_path.read_bytes() == content + assert not (doc_app2_path / file_name).exists() + assert not (doc_app2_path / "tmp1").exists() + + # Update the document contents outside fuse fd, ensure this is propagated + content = b"content3" + write_bytes_atomic(file_path, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert file_path.read_bytes() == content + assert not (doc_app2_path / file_name).exists() + assert not (doc_app2_path / "tmp1").exists() + + # Try to update the doc from an app that can't write to it + with pytest.raises(PermissionError): + (doc_app1_path / file_name).write_bytes(b"content4") + + # Update the doc from an app with write access + documents_intf.GrantPermissions(doc_id, "com.test.App1", ["write"]) + content = b"content5" + write_bytes_atomic(doc_app1_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert file_path.read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + # Try to create a tmp file for an app + assert not (doc_app1_path / "tmp3").exists() + write_bytes_atomic(doc_app1_path / "tmp3", b"tmpdata2") + (doc_app1_path / "tmp3").read_bytes() == b"tmpdata2" + assert not (doc_path / "tmp3").exists() + + # Re-Create a file from a fuse document file, in various ways + doc_id2 = export_file(documents_intf, (doc_path / file_name)) + assert doc_id2 == doc_id + doc_id3 = export_file(documents_intf, (doc_app1_path / file_name)) + assert doc_id3 == doc_id + doc_id4 = export_file(documents_intf, file_path) + assert doc_id4 == doc_id + + # Ensure we can make a unique document + doc_id5 = export_file(documents_intf, file_path, unique=True) + assert doc_id5 != doc_id + + def test_recursive_doc(self, xdg_document_portal, dbus_con): + documents_intf = xdp.get_document_portal_iface(dbus_con) + mountpoint = get_mountpoint(documents_intf) + + content = b"content" + file_name = "recursive-file" + + file_path = Path(os.environ["TMPDIR"]) / file_name + write_bytes_atomic(file_path, content) + doc_id = export_file(documents_intf, file_path) + + doc_path = mountpoint / doc_id + doc_app1_path = mountpoint / "by-app" / "com.test.App1" / doc_id + + assert (doc_path / file_name).read_bytes() == content + + doc_id2 = export_file(documents_intf, doc_path / file_name) + assert doc_id2 == doc_id + + documents_intf.GrantPermissions(doc_id, "com.test.App1", ["read"]) + + doc_id3 = export_file(documents_intf, doc_app1_path / file_name) + assert doc_id3 == doc_id + + def test_create_docs(self, xdg_document_portal, dbus_con): + documents_intf = xdp.get_document_portal_iface(dbus_con) + mountpoint = get_mountpoint(documents_intf) + + files = { + "doc1": b"doc1-content", + "doc2": b"doc2-content", + } + + file_paths = [] + for file_name, file_content in files.items(): + file_path = Path(os.environ["TMPDIR"]) / file_name + write_bytes_atomic(file_path, file_content) + file_paths.append(file_path) + + doc_ids, extra = export_files( + documents_intf, file_paths, ["read"], app_id="org.other.App" + ) + + assert extra + out_mountpoint = path_from_null_term_bytes(extra["mountpoint"]) + assert out_mountpoint == mountpoint + + assert doc_ids + for doc_id, (file_name, file_content) in zip(doc_ids, files.items()): + assert (mountpoint / doc_id / file_name).read_bytes() == file_content + assert (Path(os.environ["TMPDIR"]) / file_name).read_bytes() == file_content + app1_path = mountpoint / "by-app" / "com.test.App1" / doc_id / file_name + app2_path = mountpoint / "by-app" / "com.test.App2" / doc_id / file_name + assert not app1_path.exists() + assert not app2_path.exists() + assert not (mountpoint / doc_id / "another-file").exists() + assert not (mountpoint / "anotherid" / file_name).exists() + + other_app_path = ( + mountpoint / "by-app" / "org.other.App" / doc_id / file_name + ) + assert other_app_path.read_bytes() == file_content + with pytest.raises(PermissionError): + other_app_path.write_bytes(b"new-content") + + def test_add_named(self, xdg_document_portal, dbus_con): + documents_intf = xdp.get_document_portal_iface(dbus_con) + mountpoint = get_mountpoint(documents_intf) + + content = b"content" + file_name = "add-named-1" + + folder_path = Path(os.environ["TMPDIR"]) + doc_id = export_file_named(documents_intf, folder_path, file_name) + assert doc_id + + doc_path = mountpoint / doc_id + doc_app1_path = mountpoint / "by-app" / "com.test.App1" / doc_id + doc_app2_path = mountpoint / "by-app" / "com.test.App2" / doc_id + + assert doc_path.exists() + assert not doc_app1_path.exists() + assert not (doc_path / file_name).exists() + assert not (doc_app1_path / file_name).exists() + + documents_intf.GrantPermissions(doc_id, "com.test.App1", ["read", "write"]) + + assert doc_path.exists() + assert doc_app1_path.exists() + assert not (doc_path / file_name).exists() + assert not (doc_app1_path / file_name).exists() + + # Update truncating with no previous file + write_bytes_trunc(doc_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + # Update truncating with previous file + content = b"content2" + write_bytes_trunc(doc_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + # Update atomic with previous file + content = b"content3" + write_bytes_atomic(doc_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + # Update from host + content = b"content4" + write_bytes_atomic(folder_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + # Unlink doc + (doc_path / file_name).unlink() + assert doc_path.exists() + assert doc_app1_path.exists() + assert not (doc_path / file_name).exists() + assert not (doc_app1_path / file_name).exists() + + # Update atomic with no previous file + content = b"content5" + write_bytes_atomic(doc_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + # Unlink doc on host + (folder_path / file_name).unlink() + assert doc_path.exists() + assert doc_app1_path.exists() + assert not (doc_path / file_name).exists() + assert not (doc_app1_path / file_name).exists() + + # Update atomic with unexpected no previous file + content = b"content6" + write_bytes_atomic(doc_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + # Unlink doc on host again + (folder_path / file_name).unlink() + assert doc_path.exists() + assert doc_app1_path.exists() + assert not (doc_path / file_name).exists() + assert not (doc_app1_path / file_name).exists() + + # Update truncating with unexpected no previous file + content = b"content7" + write_bytes_trunc(doc_path / file_name, content) + assert (doc_path / file_name).read_bytes() == content + assert (doc_app1_path / file_name).read_bytes() == content + assert not (doc_app2_path / file_name).exists() + + def test_get_host_paths(self, xdg_document_portal, dbus_con): + documents_intf = xdp.get_document_portal_iface(dbus_con) + + content = b"content" + file_name = "host-path" + + file_path = Path(os.environ["TMPDIR"]) / file_name + write_bytes_atomic(file_path, content) + doc_id = export_file(documents_intf, file_path) + + host_paths = documents_intf.GetHostPaths([doc_id], byte_arrays=True) + assert doc_id in host_paths + doc_host_path = path_from_null_term_bytes(host_paths[doc_id]) + assert doc_host_path == file_path + + def test_host_paths_xattr(self, xdg_document_portal, dbus_con): + documents_intf = xdp.get_document_portal_iface(dbus_con) + mountpoint = get_mountpoint(documents_intf) + + base_path = Path(os.environ["TMPDIR"]) / "a" + file_path = base_path / "b" / "c" + file_path.parent.mkdir(parents=True, exist_ok=True) + file_path.write_bytes(b"test") + + doc_ids, extra = export_files( + documents_intf, [base_path], ["read"], flags=EXPORT_FILES_FLAG_EXPORT_DIR + ) + doc_id = doc_ids[0] + + host_path = get_host_path_attr(mountpoint / doc_id) + assert not host_path + + host_path = get_host_path_attr(mountpoint / doc_id / "a") + assert host_path == base_path + + host_path = get_host_path_attr(mountpoint / doc_id / "a" / "b") + assert host_path == base_path / "b" + + host_path = get_host_path_attr(mountpoint / doc_id / "a" / "b" / "c") + assert host_path == base_path / "b" / "c" + + +try: + xdp.ensure_fuse_supported() +except xdp.FuseNotSupportedException as e: + pytest.skip(f"No fuse support: {e}", allow_module_level=True) diff --git a/tests/test_dynamiclauncher.py b/tests/test_dynamiclauncher.py new file mode 100644 index 0000000..79f64aa --- /dev/null +++ b/tests/test_dynamiclauncher.py @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import pytest +import dbus +import os +from pathlib import Path + + +SVG_IMAGE_DATA = """ + +""" + +DESKTOP_FILE = b"""[Desktop Entry] +Version=1.0 +Name=Dynamic Launcher Example +Exec=true %u +Type=Application +""" + + +@pytest.fixture +def required_templates(): + return {"dynamiclauncher": {}} + + +class TestDynamicLauncher: + def test_version(self, portals, dbus_con): + """tests the version of the interface""" + + xdp.check_version(dbus_con, "DynamicLauncher", 1) + + def test_basic(self, portals, dbus_con, app_id): + """test that the backend receives the expected data""" + + dynlauncher_intf = xdp.get_portal_iface(dbus_con, "DynamicLauncher") + mock_intf = xdp.get_mock_iface(dbus_con) + + app_name = "App Name" + bytes = SVG_IMAGE_DATA.encode("utf-8") + + request = xdp.Request(dbus_con, dynlauncher_intf) + options = { + "modal": False, + } + response = request.call( + "PrepareInstall", + parent_window="", + name=app_name, + icon_v=dbus.Struct( + ("bytes", dbus.ByteArray(bytes, variant_level=1)), + signature="sv", + variant_level=1, + ), + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["name"] == app_name + token = response.results["token"] + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("PrepareInstall") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[2] == "" # parent window + assert args[3] == app_name # name + # args[4] == icon + assert not args[5]["modal"] + + desktop_file_name = app_id + ".ExampleApp.desktop" + dynlauncher_intf.Install( + token, + desktop_file_name, + DESKTOP_FILE, + {}, + ) + + file = Path(os.environ["XDG_DATA_HOME"]) / "applications" / desktop_file_name + assert file.exists() diff --git a/tests/test_email.py b/tests/test_email.py index d5b5dd0..f240db3 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -2,32 +2,35 @@ # # This file is formatted with Python Black -from tests import PortalMock +import tests as xdp + import dbus import pytest import time @pytest.fixture -def portal_name(): - yield "Email" +def required_templates(): + return {"email": {}} -@pytest.fixture -def portal_has_impl(): - yield True +class TestEmail: + def test_version(self, portals, dbus_con): + """tests the version of the interface""" + xdp.check_version(dbus_con, "Email", 4) -class TestEmail: - def test_version(self, portal_mock): - portal_mock.check_version(4) + def test_basic(self, portals, dbus_con): + """test that the backend receives the expected data""" + + email_intf = xdp.get_portal_iface(dbus_con, "Email") + mock_intf = xdp.get_mock_iface(dbus_con) - def test_email_basic(self, portal_mock): addresses = ["mclasen@redhat.com"] subject = "Re: portal tests" body = "You have to see this" - request = portal_mock.create_request() + request = xdp.Request(dbus_con, email_intf) options = { "addresses": addresses, "subject": subject, @@ -39,10 +42,11 @@ def test_email_basic(self, portal_mock): options=options, ) + assert response assert response.response == 0 # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("ComposeEmail") + method_calls = mock_intf.GetMethodCalls("ComposeEmail") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[2] == "" # parent window @@ -50,36 +54,44 @@ def test_email_basic(self, portal_mock): assert args[3]["subject"] == subject assert args[3]["body"] == body - def test_email_address(self, portal_mock): + def test_address(self, portals, dbus_con): """test that an invalid address triggers an error""" + email_intf = xdp.get_portal_iface(dbus_con, "Email") + mock_intf = xdp.get_mock_iface(dbus_con) + addresses = ["gibberish! not an email address\n%Q"] - request = portal_mock.create_request() + request = xdp.Request(dbus_con, email_intf) options = { "addresses": addresses, } - try: + with pytest.raises(dbus.exceptions.DBusException) as excinfo: request.call( "ComposeEmail", parent_window="", options=options, ) - - assert False, "This statement should not be reached" - except dbus.exceptions.DBusException as e: - assert e.get_dbus_name() == "org.freedesktop.portal.Error.InvalidArgument" + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) # Check the impl portal was never called - method_calls = portal_mock.mock_interface.GetMethodCalls("ComposeEmail") + method_calls = mock_intf.GetMethodCalls("ComposeEmail") assert len(method_calls) == 0 - def test_email_punycode_address(self, portal_mock): + def test_punycode_address(self, portals, dbus_con): + """test email address containing punycode""" + + email_intf = xdp.get_portal_iface(dbus_con, "Email") + mock_intf = xdp.get_mock_iface(dbus_con) + addresses = ["xn--franais-xxa@exemple.fr"] subject = "Re: portal tests" body = "To ASCII and beyond" - request = portal_mock.create_request() + request = xdp.Request(dbus_con, email_intf) options = { "addresses": addresses, "subject": subject, @@ -91,10 +103,11 @@ def test_email_punycode_address(self, portal_mock): options=options, ) + assert response assert response.response == 0 # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("ComposeEmail") + method_calls = mock_intf.GetMethodCalls("ComposeEmail") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[2] == "" # parent window @@ -102,67 +115,77 @@ def test_email_punycode_address(self, portal_mock): assert args[3]["subject"] == subject assert args[3]["body"] == body - def test_email_subject_multiline(self, portal_mock): + def test_subject_multiline(self, portals, dbus_con): """test that an multiline subject triggers an error""" + email_intf = xdp.get_portal_iface(dbus_con, "Email") + mock_intf = xdp.get_mock_iface(dbus_con) + subject = "not\na\nvalid\nsubject line" - request = portal_mock.create_request() + request = xdp.Request(dbus_con, email_intf) options = { "subject": subject, } - try: + with pytest.raises(dbus.exceptions.DBusException) as excinfo: request.call( "ComposeEmail", parent_window="", options=options, ) - - assert False, "This statement should not be reached" - except dbus.exceptions.DBusException as e: - assert e.get_dbus_name() == "org.freedesktop.portal.Error.InvalidArgument" + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) # Check the impl portal was never called - method_calls = portal_mock.mock_interface.GetMethodCalls("ComposeEmail") + method_calls = mock_intf.GetMethodCalls("ComposeEmail") assert len(method_calls) == 0 - def test_email_subject_too_long(self, portal_mock): + def test_subject_too_long(self, portals, dbus_con): """test that a subject line over 200 chars triggers an error""" + email_intf = xdp.get_portal_iface(dbus_con, "Email") + mock_intf = xdp.get_mock_iface(dbus_con) + subject = "This subject line is too long" + "abc" * 60 assert len(subject) > 200 - request = portal_mock.create_request() + request = xdp.Request(dbus_con, email_intf) options = { "subject": subject, } - try: + with pytest.raises(dbus.exceptions.DBusException) as excinfo: request.call( "ComposeEmail", parent_window="", options=options, ) - - assert False, "This statement should not be reached" - except dbus.exceptions.DBusException as e: - assert e.get_dbus_name() == "org.freedesktop.portal.Error.InvalidArgument" + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) # Check the impl portal was never called - method_calls = portal_mock.mock_interface.GetMethodCalls("ComposeEmail") + method_calls = mock_intf.GetMethodCalls("ComposeEmail") assert len(method_calls) == 0 - @pytest.mark.parametrize("params", ({"delay": 2000},)) - def test_email_delay(self, portal_mock): + @pytest.mark.parametrize("template_params", ({"email": {"delay": 2000}},)) + def test_delay(self, portals, dbus_con): """ Test that everything works as expected when the backend takes some time to send its response, as * is to be expected from a real backend that presents dialogs to the user. """ + + email_intf = xdp.get_portal_iface(dbus_con, "Email") + mock_intf = xdp.get_mock_iface(dbus_con) + subject = "delay test" addresses = ["mclasen@redhat.com"] - request = portal_mock.create_request() + request = xdp.Request(dbus_con, email_intf) options = { "addresses": addresses, "subject": subject, @@ -176,6 +199,7 @@ def test_email_delay(self, portal_mock): options=options, ) + assert response assert response.response == 0 end_time = time.perf_counter() @@ -183,15 +207,15 @@ def test_email_delay(self, portal_mock): assert end_time - start_time > 2 # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("ComposeEmail") + method_calls = mock_intf.GetMethodCalls("ComposeEmail") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[2] == "" # parent window assert args[3]["addresses"] == addresses assert args[3]["subject"] == subject - @pytest.mark.parametrize("params", ({"response": 1},)) - def test_email_cancel(self, portal_mock): + @pytest.mark.parametrize("template_params", ({"email": {"response": 1}},)) + def test_cancel(self, portals, dbus_con): """ Test that user cancellation works as expected. We simulate that the user cancels a hypothetical dialog, @@ -199,10 +223,13 @@ def test_email_cancel(self, portal_mock): And we check that we get the expected G_IO_ERROR_CANCELLED. """ + email_intf = xdp.get_portal_iface(dbus_con, "Email") + mock_intf = xdp.get_mock_iface(dbus_con) + subject = "cancel test" addresses = ["mclasen@redhat.com"] - request = portal_mock.create_request() + request = xdp.Request(dbus_con, email_intf) options = { "addresses": addresses, "subject": subject, @@ -214,18 +241,19 @@ def test_email_cancel(self, portal_mock): options=options, ) + assert response assert response.response == 1 # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("ComposeEmail") + method_calls = mock_intf.GetMethodCalls("ComposeEmail") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[2] == "" # parent window assert args[3]["addresses"] == addresses assert args[3]["subject"] == subject - @pytest.mark.parametrize("params", ({"expect-close": True},)) - def test_email_close(self, portal_mock): + @pytest.mark.parametrize("template_params", ({"email": {"expect-close": True}},)) + def test_close(self, portals, dbus_con): """ Test that app-side cancellation works as expected. We cancel the cancellable while while the hypothetical @@ -234,10 +262,13 @@ def test_email_close(self, portal_mock): verify that that call actually happened. """ + email_intf = xdp.get_portal_iface(dbus_con, "Email") + mock_intf = xdp.get_mock_iface(dbus_con) + subject = "close test" addresses = ["mclasen@redhat.com"] - request = portal_mock.create_request() + request = xdp.Request(dbus_con, email_intf) request.schedule_close(1000) options = { "addresses": addresses, @@ -254,7 +285,7 @@ def test_email_close(self, portal_mock): assert request.closed # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("ComposeEmail") + method_calls = mock_intf.GetMethodCalls("ComposeEmail") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[2] == "" # parent window diff --git a/tests/test_filechooser.py b/tests/test_filechooser.py new file mode 100644 index 0000000..775288e --- /dev/null +++ b/tests/test_filechooser.py @@ -0,0 +1,591 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import dbus +import pytest + + +FILECHOOSER_RESULTS = { + "uris": ["file:///test.txt", "file:///example/test2.txt"], + "choices": [("encoding", "utf8"), ("reencode", "true"), ("third", "a")], +} + + +@pytest.fixture +def required_templates(): + return { + "filechooser": { + "results": dbus.Dictionary(FILECHOOSER_RESULTS, signature="sv"), + }, + "lockdown": {}, + } + + +class TestFilechooser: + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "FileChooser", 4) + + def test_open_file_basic(self, portals, dbus_con, app_id): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test" + accept_label = "Accept" + multiple = True + options = { + "accept_label": accept_label, + "multiple": multiple, + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "OpenFile", + parent_window="", + title=title, + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["uris"] == FILECHOOSER_RESULTS["uris"] + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("OpenFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + assert args[4]["accept_label"] == accept_label + assert args[4]["multiple"] == multiple + + @pytest.mark.parametrize("template_params", ({"filechooser": {"response": 1}},)) + def test_open_file_cancel(self, portals, dbus_con, app_id): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test" + accept_label = "Accept" + multiple = True + options = { + "accept_label": accept_label, + "multiple": multiple, + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "OpenFile", + parent_window="", + title=title, + options=options, + ) + + assert response + assert response.response == 1 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("OpenFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + assert args[4]["accept_label"] == accept_label + assert args[4]["multiple"] == multiple + + @pytest.mark.parametrize( + "template_params", ({"filechooser": {"expect-close": True}},) + ) + def test_open_file_close(self, portals, dbus_con, app_id): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test" + accept_label = "Accept" + multiple = True + options = { + "accept_label": accept_label, + "multiple": multiple, + } + request = xdp.Request(dbus_con, filechooser_intf) + request.schedule_close(1000) + request.call( + "OpenFile", + parent_window="", + title=title, + options=options, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("OpenFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + assert args[4]["accept_label"] == accept_label + assert args[4]["multiple"] == multiple + + def test_open_file_filter1(self, portals, dbus_con): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + options = { + "filters": [ + ( + "Images", + [ + (dbus.UInt32(0), "*ico"), + (dbus.UInt32(1), "image/png"), + ], + ), + ( + "Text", + [ + (dbus.UInt32(0), "*.txt"), + ], + ), + ], + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "OpenFile", + parent_window="", + title="Test", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["uris"] == FILECHOOSER_RESULTS["uris"] + + method_calls = mock_intf.GetMethodCalls("OpenFile") + assert len(method_calls) == 1 + + def test_open_file_filter2(self, portals, dbus_con): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + + options = { + "filters": [ + ( + "Text", + [ + # Invalid filter type + (dbus.UInt32(4), "*.txt"), + ], + ), + ], + } + request = xdp.Request(dbus_con, filechooser_intf) + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "OpenFile", + parent_window="", + title="Test", + options=options, + ) + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) + + def test_open_file_current_filter1(self, portals, dbus_con): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + options = { + "filters": [ + ( + "Images", + [ + (dbus.UInt32(0), "*ico"), + (dbus.UInt32(1), "image/png"), + ], + ), + ( + "Text", + [ + (dbus.UInt32(0), "*.txt"), + ], + ), + ], + "current_filter": ( + "Text", + [ + (dbus.UInt32(0), "*.txt"), + ], + ), + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "OpenFile", + parent_window="", + title="Test", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["uris"] == FILECHOOSER_RESULTS["uris"] + + method_calls = mock_intf.GetMethodCalls("OpenFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[4]["current_filter"] == options["current_filter"] + + def test_open_file_current_filter2(self, portals, dbus_con): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + options = { + "current_filter": ( + "Text", + [ + (dbus.UInt32(0), "*.txt"), + ], + ), + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "OpenFile", + parent_window="", + title="Test", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["uris"] == FILECHOOSER_RESULTS["uris"] + + method_calls = mock_intf.GetMethodCalls("OpenFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[4]["current_filter"] == options["current_filter"] + + def test_open_file_current_filter3(self, portals, dbus_con): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + + options = { + "current_filter": ( + "Text", + [ + # Invalid filter type + (dbus.UInt32(6), "*.txt"), + ], + ), + } + request = xdp.Request(dbus_con, filechooser_intf) + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "OpenFile", + parent_window="", + title="Test", + options=options, + ) + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) + + def test_open_file_current_filter4(self, portals, dbus_con): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + + options = { + "filters": [ + ( + "Images", + [ + (dbus.UInt32(0), "*ico"), + (dbus.UInt32(1), "image/png"), + ], + ), + ( + "Text", + [ + (dbus.UInt32(0), "*.txt"), + ], + ), + ], + "current_filter": ( + "Something else", + [ + (dbus.UInt32(0), "*.sth.else"), + ], + ), + } + request = xdp.Request(dbus_con, filechooser_intf) + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "OpenFile", + parent_window="", + title="Test", + options=options, + ) + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) + + def test_open_file_choices1(self, portals, dbus_con): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + options = { + "choices": [ + ( + "encoding", + "Encoding", + [ + ("utf8", "Unicode"), + ("latin15", "Western"), + ], + "latin15", + ), + ( + "reencode", + "Reencode", + [], + "false", + ), + ( + "third", + "Third", + [("a", "A"), ("b", "B")], + "", + ), + ], + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "OpenFile", + parent_window="", + title="Test", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["uris"] == FILECHOOSER_RESULTS["uris"] + + method_calls = mock_intf.GetMethodCalls("OpenFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[4]["choices"] == options["choices"] + + def test_open_file_choices_invalid(self, portals, dbus_con): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + + invalid_choices = [ + ( + "encoding", + "Encoding", + [ + ("utf8", ""), + ("latin15", "Western"), + ], + "latin15", + ), + ( + "encoding", + "Encoding", + [ + ("", "Unicode"), + ("latin15", "Western"), + ], + "latin15", + ), + ( + "", + "Encoding", + [ + ("utf8", "Unicode"), + ("latin15", "Western"), + ], + "latin15", + ), + ] + + for choice in invalid_choices: + request = xdp.Request(dbus_con, filechooser_intf) + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + options = { + "choices": [choice], + } + request.call( + "OpenFile", + parent_window="", + title="Test", + options=options, + ) + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) + + def test_save_file_basic(self, portals, dbus_con, app_id): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test" + accept_label = "Accept" + current_name = "File Name" + options = { + "accept_label": accept_label, + "current_name": current_name, + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "SaveFile", + parent_window="", + title=title, + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["uris"] == FILECHOOSER_RESULTS["uris"] + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("SaveFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + assert args[4]["accept_label"] == accept_label + assert args[4]["current_name"] == current_name + + @pytest.mark.parametrize("template_params", ({"filechooser": {"response": 1}},)) + def test_save_file_cancel(self, portals, dbus_con, app_id): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test" + accept_label = "Accept" + current_name = "File Name" + options = { + "accept_label": accept_label, + "current_name": current_name, + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "SaveFile", + parent_window="", + title=title, + options=options, + ) + + assert response + assert response.response == 1 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("SaveFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + assert args[4]["accept_label"] == accept_label + assert args[4]["current_name"] == current_name + + @pytest.mark.parametrize( + "template_params", ({"filechooser": {"expect-close": True}},) + ) + def test_save_file_close(self, portals, dbus_con, app_id): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test" + accept_label = "Accept" + current_name = "File Name" + options = { + "accept_label": accept_label, + "current_name": current_name, + } + request = xdp.Request(dbus_con, filechooser_intf) + request.schedule_close(1000) + request.call( + "SaveFile", + parent_window="", + title=title, + options=options, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("SaveFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + assert args[4]["accept_label"] == accept_label + assert args[4]["current_name"] == current_name + + def test_save_file_filters(self, portals, dbus_con, app_id): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + options = { + "filters": [ + ( + "Images", + [ + (dbus.UInt32(0), "*ico"), + (dbus.UInt32(1), "image/png"), + ], + ), + ( + "Text", + [ + (dbus.UInt32(0), "*.txt"), + ], + ), + ], + } + request = xdp.Request(dbus_con, filechooser_intf) + response = request.call( + "SaveFile", + parent_window="", + title="Title", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["uris"] == FILECHOOSER_RESULTS["uris"] + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("SaveFile") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[4]["filters"] == options["filters"] + + @pytest.mark.parametrize( + "template_params", ({"lockdown": {"disable-save-to-disk": True}},) + ) + def test_save_file_lockdown(self, portals, dbus_con, app_id): + filechooser_intf = xdp.get_portal_iface(dbus_con, "FileChooser") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, filechooser_intf) + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "SaveFile", + parent_window="", + title="Title", + options={}, + ) + assert ( + excinfo.value.get_dbus_name() == "org.freedesktop.portal.Error.NotAllowed" + ) + + # Check the impl portal was not called + method_calls = mock_intf.GetMethodCalls("FileChooser") + assert len(method_calls) == 0 diff --git a/tests/test_globalshortcuts.py b/tests/test_globalshortcuts.py index 56349cb..1516530 100644 --- a/tests/test_globalshortcuts.py +++ b/tests/test_globalshortcuts.py @@ -2,9 +2,7 @@ # # This file is formatted with Python Black - -from tests import PortalMock, Session -from gi.repository import GLib +import tests as xdp import dbus import pytest @@ -12,16 +10,19 @@ @pytest.fixture -def portal_name(): - return "GlobalShortcuts" +def required_templates(): + return {"globalshortcuts": {}} class TestGlobalShortcuts: - def test_version(self, portal_mock): - portal_mock.check_version(1) + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "GlobalShortcuts", 1) + + def test_create_close_session(self, portals, dbus_con, app_id): + globalshortcuts_intf = xdp.get_portal_iface(dbus_con, "GlobalShortcuts") + mock_intf = xdp.get_mock_iface(dbus_con) - def test_global_shortcuts_create_close_session(self, portal_mock): - request = portal_mock.create_request() + request = xdp.Request(dbus_con, globalshortcuts_intf) options = { "session_handle_token": "session_token0", } @@ -30,27 +31,28 @@ def test_global_shortcuts_create_close_session(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) + session = xdp.Session.from_response(dbus_con, response) # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[1] == session.handle - assert args[2] == "" # appid + assert args[2] == app_id session.close() + xdp.wait_for(lambda: session.closed) - mainloop = GLib.MainLoop() - GLib.timeout_add(2000, mainloop.quit) - mainloop.run() + @pytest.mark.parametrize( + "template_params", ({"globalshortcuts": {"force-close": 500}},) + ) + def test_create_session_signal_closed(self, portals, dbus_con, app_id): + globalshortcuts_intf = xdp.get_portal_iface(dbus_con, "GlobalShortcuts") + mock_intf = xdp.get_mock_iface(dbus_con) - assert session.closed - - @pytest.mark.parametrize("params", ({"force-close": 500},)) - def test_global_shortcuts_create_session_signal_closed(self, portal_mock): - request = portal_mock.create_request() + request = xdp.Request(dbus_con, globalshortcuts_intf) options = { "session_handle_token": "session_token0", } @@ -59,26 +61,24 @@ def test_global_shortcuts_create_session_signal_closed(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) + session = xdp.Session.from_response(dbus_con, response) # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[1] == session.handle - assert args[2] == "" # appid + assert args[2] == app_id # Now expect the backend to close it + xdp.wait_for(lambda: session.closed) - mainloop = GLib.MainLoop() - GLib.timeout_add(2000, mainloop.quit) - mainloop.run() - - assert session.closed + def test_bind_list_shortcuts(self, portals, dbus_con): + globalshortcuts_intf = xdp.get_portal_iface(dbus_con, "GlobalShortcuts") - def test_global_shortcuts_bind_list_shortcuts(self, portal_mock): - request = portal_mock.create_request() + request = xdp.Request(dbus_con, globalshortcuts_intf) options = { "session_handle_token": "session_token0", } @@ -87,9 +87,10 @@ def test_global_shortcuts_bind_list_shortcuts(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) + session = xdp.Session.from_response(dbus_con, response) shortcuts = [ ( @@ -108,7 +109,7 @@ def test_global_shortcuts_bind_list_shortcuts(self, portal_mock): ), ] - request = portal_mock.create_request() + request = xdp.Request(dbus_con, globalshortcuts_intf) response = request.call( "BindShortcuts", session_handle=session.handle, @@ -117,7 +118,10 @@ def test_global_shortcuts_bind_list_shortcuts(self, portal_mock): options={}, ) - request = portal_mock.create_request() + assert response + assert response.response == 0 + + request = xdp.Request(dbus_con, globalshortcuts_intf) options = {} response = request.call( "ListShortcuts", @@ -125,18 +129,19 @@ def test_global_shortcuts_bind_list_shortcuts(self, portal_mock): options=options, ) + assert response + assert response.response == 0 + assert len(list(response.results["shortcuts"])) == len(list(shortcuts)) session.close() + xdp.wait_for(lambda: session.closed) - mainloop = GLib.MainLoop() - GLib.timeout_add(2000, mainloop.quit) - mainloop.run() - - assert session.closed + def test_trigger(self, portals, dbus_con): + globalshortcuts_intf = xdp.get_portal_iface(dbus_con, "GlobalShortcuts") + mock_intf = xdp.get_mock_iface(dbus_con) - def test_global_shortcuts_trigger(self, portal_mock): - request = portal_mock.create_request() + request = xdp.Request(dbus_con, globalshortcuts_intf) options = { "session_handle_token": "session_token0", } @@ -145,9 +150,10 @@ def test_global_shortcuts_trigger(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) + session = xdp.Session.from_response(dbus_con, response) shortcuts = [ ( @@ -159,7 +165,7 @@ def test_global_shortcuts_trigger(self, portal_mock): ), ] - request = portal_mock.create_request() + request = xdp.Request(dbus_con, globalshortcuts_intf) response = request.call( "BindShortcuts", session_handle=session.handle, @@ -168,6 +174,9 @@ def test_global_shortcuts_trigger(self, portal_mock): options={}, ) + assert response + assert response.response == 0 + activated_count = 0 deactivated_count = 0 @@ -195,23 +204,13 @@ def cb_deactivated(session_handle, shortcut_id, timestamp, options): assert shortcut_id == "binding1" deactivated_count += 1 - gs_intf = portal_mock.get_dbus_interface() - gs_intf.connect_to_signal("Activated", cb_activated) - gs_intf.connect_to_signal("Deactivated", cb_deactivated) + globalshortcuts_intf.connect_to_signal("Activated", cb_activated) + globalshortcuts_intf.connect_to_signal("Deactivated", cb_deactivated) - portal_mock.mock_interface.Trigger(session.handle, "binding1") + mock_intf.Trigger(session.handle, "binding1") - mainloop = GLib.MainLoop() - GLib.timeout_add(2000, mainloop.quit) - mainloop.run() - - assert activated_count == 1 - assert deactivated_count == 1 + xdp.wait_for(lambda: activated_count == 1 and deactivated_count == 1) + assert not session.closed session.close() - - mainloop = GLib.MainLoop() - GLib.timeout_add(2000, mainloop.quit) - mainloop.run() - - assert session.closed + xdp.wait_for(lambda: session.closed) diff --git a/tests/test_inhibit.py b/tests/test_inhibit.py new file mode 100644 index 0000000..e7ae6bd --- /dev/null +++ b/tests/test_inhibit.py @@ -0,0 +1,242 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import pytest +from enum import Enum, Flag + + +class InhibitFlags(Flag): + LOGOUT = 1 + USER_SWITCH = 2 + SUSPEND = 4 + IDLE = 8 + ALL = 16 - 1 + + +class SessionState(Enum): + RUNNING = 1 + QUERY_END = 2 + ENDING = 3 + + +@pytest.fixture +def required_templates(): + return {"inhibit": {}} + + +class TestInhibit: + def set_permissions(self, dbus_con, app_id, permissions): + perm_store_intf = xdp.get_permission_store_iface(dbus_con) + perm_store_intf.SetPermission( + "inhibit", + True, + "inhibit", + app_id, + permissions, + ) + + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Inhibit", 3) + + def test_basic(self, portals, dbus_con, app_id): + inhibit_intf = xdp.get_portal_iface(dbus_con, "Inhibit") + mock_intf = xdp.get_mock_iface(dbus_con) + + reason = "reason" + flags = InhibitFlags.ALL + + request = xdp.Request(dbus_con, inhibit_intf) + options = { + "reason": reason, + } + response = request.call( + "Inhibit", + window="", + flags=flags.value, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Inhibit") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == flags.value + assert args[4]["reason"] == reason + + @pytest.mark.parametrize("template_params", ({"inhibit": {"response": 1}},)) + def test_cancel(self, portals, dbus_con, app_id): + inhibit_intf = xdp.get_portal_iface(dbus_con, "Inhibit") + mock_intf = xdp.get_mock_iface(dbus_con) + + reason = "reason" + flags = InhibitFlags.ALL + + request = xdp.Request(dbus_con, inhibit_intf) + options = { + "reason": reason, + } + response = request.call( + "Inhibit", + window="", + flags=flags.value, + options=options, + ) + + # for some reason, the backend failing is still considered a success + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Inhibit") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == flags.value + assert args[4]["reason"] == reason + + @pytest.mark.parametrize("template_params", ({"inhibit": {"expect-close": True}},)) + def test_close(self, portals, dbus_con, app_id): + inhibit_intf = xdp.get_portal_iface(dbus_con, "Inhibit") + mock_intf = xdp.get_mock_iface(dbus_con) + + reason = "reason" + flags = InhibitFlags.ALL + + request = xdp.Request(dbus_con, inhibit_intf) + request.schedule_close(1000) + options = { + "reason": reason, + } + request.call( + "Inhibit", + window="", + flags=flags.value, + options=options, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Inhibit") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == flags.value + assert args[4]["reason"] == reason + + def test_permission(self, portals, dbus_con, app_id): + inhibit_intf = xdp.get_portal_iface(dbus_con, "Inhibit") + mock_intf = xdp.get_mock_iface(dbus_con) + + self.set_permissions(dbus_con, app_id, ["logout", "suspend"]) + + reason = "reason" + flags = InhibitFlags.LOGOUT | InhibitFlags.SUSPEND | InhibitFlags.IDLE + allowed_flags = InhibitFlags.LOGOUT | InhibitFlags.SUSPEND + + request = xdp.Request(dbus_con, inhibit_intf) + options = { + "reason": reason, + } + response = request.call( + "Inhibit", + window="", + flags=flags.value, + options=options, + ) + + assert response + assert response.response == 0 + + method_calls = mock_intf.GetMethodCalls("Inhibit") + _, args = method_calls[-1] + assert args[3] == allowed_flags.value + + self.set_permissions(dbus_con, app_id, ["suspend"]) + + flags = InhibitFlags.LOGOUT | InhibitFlags.SUSPEND | InhibitFlags.IDLE + allowed_flags = InhibitFlags.SUSPEND + + request = xdp.Request(dbus_con, inhibit_intf) + options = { + "reason": reason, + } + response = request.call( + "Inhibit", + window="", + flags=flags.value, + options=options, + ) + + assert response + assert response.response == 0 + + method_calls = mock_intf.GetMethodCalls("Inhibit") + _, args = method_calls[-1] + assert args[3] == allowed_flags.value + + def test_monitor(self, portals, dbus_con, app_id): + inhibit_intf = xdp.get_portal_iface(dbus_con, "Inhibit") + mock_intf = xdp.get_mock_iface(dbus_con) + + changed_count = 0 + + request = xdp.Request(dbus_con, inhibit_intf) + options = { + "session_handle_token": "session_token0", + } + response = request.call( + "CreateMonitor", + window="", + options=options, + ) + + assert response + assert response.response == 0 + + session = xdp.Session.from_response(dbus_con, response) + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("CreateMonitor") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == session.handle + assert args[2] == app_id + assert args[3] == "" # parent window + + def state_changed_cb(session_handle, state): + nonlocal changed_count + + assert not state["screensaver-active"] + assert state["session-state"] == SessionState.QUERY_END.value + + changed_count += 1 + + inhibit_intf.connect_to_signal("StateChanged", state_changed_cb) + + # wait for a Query End state change + xdp.wait_for(lambda: changed_count == 1) + assert not session.closed + # and respond with QueryEndResponse + inhibit_intf.QueryEndResponse(session.handle) + + # wait for another Query End state change + xdp.wait_for(lambda: changed_count == 2) + assert not session.closed + + # do not respond with QueryEndResponse and instead wait for >1s + xdp.wait(1500) + + # the session should have gotten closed by now + assert session.closed diff --git a/tests/test_inputcapture.py b/tests/test_inputcapture.py index 07c130d..b38e12a 100644 --- a/tests/test_inputcapture.py +++ b/tests/test_inputcapture.py @@ -2,13 +2,15 @@ # # This file is formatted with Python Black -from gi.repository import GLib - -from itertools import count +import tests as xdp import dbus import pytest import socket +from gi.repository import GLib +from itertools import count +from typing import Any + counter = count() @@ -18,8 +20,8 @@ def default_zones(): @pytest.fixture -def portal_name(): - return "InputCapture" +def required_templates(): + return {"inputcapture": {}} @pytest.fixture @@ -28,12 +30,15 @@ def zones(): class TestInputCapture: - def create_session(self, portal_mock, capabilities=0xF): + def create_session(self, dbus_con, capabilities=0xF): """ Call CreateSession for the given capabilities and return the (response, results) tuple. """ - request = portal_mock.create_request() + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, inputcapture_intf) capabilities = dbus.UInt32(capabilities, variant_level=1) session_handle_token = dbus.String(f"session{next(counter)}", variant_level=1) @@ -46,66 +51,75 @@ def create_session(self, portal_mock, capabilities=0xF): signature="sv", ) - response, results = request.call( - "CreateSession", parent_window="", options=options - ) - assert response == 0 - assert "session_handle" in results - assert "capabilities" in results - caps = results["capabilities"] + response = request.call("CreateSession", parent_window="", options=options) + + assert response + assert response.response == 0 + assert "session_handle" in response.results + assert "capabilities" in response.results + caps = response.results["capabilities"] # Returned capabilities must be a subset of the requested ones assert caps & ~capabilities == 0 - self.current_session_handle = results["session_handle"] + self.current_session_handle = response.results["session_handle"] # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[3] == "" # parent window assert args[4]["capabilities"] == capabilities - return response, results + return response - def get_zones(self, portal_mock): + def get_zones(self, dbus_con): """ Call GetZones and return the (response, results) tuple. """ - request = portal_mock.create_request() - options = {} - response, results = request.call( + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, inputcapture_intf) + options: Any = {} + response = request.call( "GetZones", session_handle=self.current_session_handle, options=options ) - assert response == 0 - assert "zones" in results - assert "zone_set" in results - self.current_zone_set = results["zone_set"] + assert response + assert response.response == 0 + assert "zones" in response.results + assert "zone_set" in response.results + + self.current_zone_set = response.results["zone_set"] # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("GetZones") + method_calls = mock_intf.GetMethodCalls("GetZones") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[0] == request.handle assert args[1] == self.current_session_handle - return response, results + return response + + def set_pointer_barriers(self, dbus_con, barriers): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + mock_intf = xdp.get_mock_iface(dbus_con) - def set_pointer_barriers(self, portal_mock, barriers): - request = portal_mock.create_request() - options = {} - response, results = request.call( + request = xdp.Request(dbus_con, inputcapture_intf) + options: Any = {} + response = request.call( "SetPointerBarriers", session_handle=self.current_session_handle, options=options, barriers=barriers, zone_set=self.current_zone_set, ) - assert response == 0 - assert "failed_barriers" in results + assert response + assert response.response == 0 + assert "failed_barriers" in response.results # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("SetPointerBarriers") + method_calls = mock_intf.GetMethodCalls("SetPointerBarriers") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[0] == request.handle @@ -113,10 +127,12 @@ def set_pointer_barriers(self, portal_mock, barriers): assert args[4] == barriers assert args[5] == self.current_zone_set - return response, results + return response + + def connect_to_eis(self, dbus_con): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + mock_intf = xdp.get_mock_iface(dbus_con) - def connect_to_eis(self, portal_mock): - inputcapture_intf = portal_mock.get_dbus_interface() fd = inputcapture_intf.ConnectToEIS( self.current_session_handle, dbus.Dictionary({}, signature="sv") ) @@ -126,48 +142,54 @@ def connect_to_eis(self, portal_mock): hello = eis_socket.recv(10) assert hello == b"HELLO" - method_calls = portal_mock.mock_interface.GetMethodCalls("ConnectToEIS") + method_calls = mock_intf.GetMethodCalls("ConnectToEIS") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[0] == self.current_session_handle return eis_socket - def enable(self, portal_mock): - inputcapture_intf = portal_mock.get_dbus_interface() + def enable(self, dbus_con): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + mock_intf = xdp.get_mock_iface(dbus_con) + inputcapture_intf.Enable( self.current_session_handle, dbus.Dictionary({}, signature="sv") ) - method_calls = portal_mock.mock_interface.GetMethodCalls("Enable") + method_calls = mock_intf.GetMethodCalls("Enable") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[0] == self.current_session_handle - def disable(self, portal_mock): - inputcapture_intf = portal_mock.get_dbus_interface() + def disable(self, dbus_con): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + mock_intf = xdp.get_mock_iface(dbus_con) + inputcapture_intf.Disable( self.current_session_handle, dbus.Dictionary({}, signature="sv") ) - method_calls = portal_mock.mock_interface.GetMethodCalls("Disable") + method_calls = mock_intf.GetMethodCalls("Disable") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[0] == self.current_session_handle - def release(self, portal_mock, activation_id: int, cursor_position=None): + def release(self, dbus_con, activation_id: int, cursor_position=None): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + mock_intf = xdp.get_mock_iface(dbus_con) + options = {"activation_id": dbus.UInt32(activation_id)} if cursor_position: options["cursor_position"] = dbus.Struct( list(cursor_position), signature="dd", variant_level=1 ) - inputcapture_intf = portal_mock.get_dbus_interface() inputcapture_intf.Release( self.current_session_handle, dbus.Dictionary(options, signature="sv") ) - method_calls = portal_mock.mock_interface.GetMethodCalls("Release") + method_calls = mock_intf.GetMethodCalls("Release") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[0] == self.current_session_handle @@ -179,98 +201,113 @@ def release(self, portal_mock, activation_id: int, cursor_position=None): pos = args[2]["cursor_position"] assert pos == cursor_position - def test_version(self, portal_mock): - portal_mock.check_version(1) + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "InputCapture", 1) @pytest.mark.parametrize( - "params", + "template_params", ( { - "supported_capabilities": 0b101, # KEYBOARD, POINTER, TOUCH + "inputcapture": { + "supported_capabilities": 0b101, # KEYBOARD, POINTER, TOUCH + }, }, ), ) - def test_supported_capabilities(self, portal_mock): - properties_intf = portal_mock.get_dbus_interface( - "org.freedesktop.DBus.Properties" - ) + def test_supported_capabilities(self, portals, dbus_con): + properties_intf = xdp.get_iface(dbus_con, "org.freedesktop.DBus.Properties") + caps = properties_intf.Get( "org.freedesktop.portal.InputCapture", "SupportedCapabilities" ) assert caps == 0b101 - def test_create_session(self, portal_mock): - self.create_session(portal_mock, capabilities=0b1) # KEYBOARD + def test_create_session(self, portals, dbus_con): + mock_intf = xdp.get_mock_iface(dbus_con) + + self.create_session(dbus_con, capabilities=0b1) # KEYBOARD # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) == 1 _, args = method_calls.pop(0) assert args[3] == "" # parent window assert args[4]["capabilities"] == 0b1 @pytest.mark.parametrize( - "params", + "template_params", ( { - "capabilities": 0b110, # TOUCH, POINTER - "supported_capabilities": 0b111, # TOUCH, POINTER, KEYBOARD + "inputcapture": { + "capabilities": 0b110, # TOUCH, POINTER + "supported_capabilities": 0b111, # TOUCH, POINTER, KEYBOARD + }, }, ), ) - def test_create_session_limited_caps(self, portal_mock): + def test_create_session_limited_caps(self, portals, dbus_con): + mock_intf = xdp.get_mock_iface(dbus_con) + # Request more caps than are supported - response, results = self.create_session(portal_mock, capabilities=0b111) + response, results = self.create_session(dbus_con, capabilities=0b111) caps = results["capabilities"] # Returned capabilities must the ones we set up in the params assert caps == 0b110 # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) == 1 _, args = method_calls.pop(0) assert args[3] == "" # parent window assert args[4]["capabilities"] == 0b111 @pytest.mark.parametrize( - "params", + "template_params", ( { - "default-zone": dbus.Array( - [dbus.Struct(z, signature="uuii") for z in default_zones()], - signature="(uuii)", - variant_level=1, - ) + "inputcapture": { + "default-zone": dbus.Array( + [dbus.Struct(z, signature="uuii") for z in default_zones()], + signature="(uuii)", + variant_level=1, + ) + }, }, ), ) - def test_get_zones(self, portal_mock, zones): - response, results = self.create_session(portal_mock) - response, results = self.get_zones(portal_mock) + def test_get_zones(self, portals, dbus_con, zones): + mock_intf = xdp.get_mock_iface(dbus_con) + + response, results = self.create_session(dbus_con) + response, results = self.get_zones(dbus_con) for z1, z2 in zip(results["zones"], zones): assert z1 == z2 # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) == 1 - method_calls = portal_mock.mock_interface.GetMethodCalls("GetZones") + method_calls = mock_intf.GetMethodCalls("GetZones") assert len(method_calls) == 1 @pytest.mark.parametrize( - "params", + "template_params", ( { - "default-zone": dbus.Array( - [dbus.Struct(z, signature="uuii") for z in default_zones()], - signature="(uuii)", - variant_level=1, - ) + "inputcapture": { + "default-zone": dbus.Array( + [dbus.Struct(z, signature="uuii") for z in default_zones()], + signature="(uuii)", + variant_level=1, + ) + }, }, ), ) - def test_set_pointer_barriers(self, portal_mock, zones): - response, results = self.create_session(portal_mock) - response, results = self.get_zones(portal_mock) + def test_set_pointer_barriers(self, portals, dbus_con, zones): + mock_intf = xdp.get_mock_iface(dbus_con) + + response, results = self.create_session(dbus_con) + response, results = self.get_zones(dbus_con) barriers = [ { @@ -347,7 +384,7 @@ def test_set_pointer_barriers(self, portal_mock, zones): ), }, ] - response, results = self.set_pointer_barriers(portal_mock, barriers=barriers) + response, results = self.set_pointer_barriers(dbus_con, barriers=barriers) failed_barriers = results["failed_barriers"] assert all([id >= 20 for id in failed_barriers]) @@ -355,19 +392,19 @@ def test_set_pointer_barriers(self, portal_mock, zones): assert id in failed_barriers # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) == 1 - method_calls = portal_mock.mock_interface.GetMethodCalls("GetZones") + method_calls = mock_intf.GetMethodCalls("GetZones") assert len(method_calls) == 1 - method_calls = portal_mock.mock_interface.GetMethodCalls("SetPointerBarriers") + method_calls = mock_intf.GetMethodCalls("SetPointerBarriers") assert len(method_calls) == 1 _, args = method_calls.pop(0) assert args[4] == barriers assert args[5] == self.current_zone_set - def test_connect_to_eis(self, portal_mock): - self.create_session(portal_mock) - self.get_zones(portal_mock) + def test_connect_to_eis(self, portals, dbus_con): + self.create_session(dbus_con) + self.get_zones(dbus_con) # The default zone is 1920x1080 barriers = [ @@ -378,14 +415,15 @@ def test_connect_to_eis(self, portal_mock): ), }, ] - self.set_pointer_barriers(portal_mock, barriers) + self.set_pointer_barriers(dbus_con, barriers) - self.connect_to_eis(portal_mock) + self.connect_to_eis(dbus_con) - def test_enable_disable(self, portal_mock): - self.create_session(portal_mock) - self.create_session(portal_mock) - self.get_zones(portal_mock) + def test_enable_disable(self, portals, dbus_con): + mock_intf = xdp.get_mock_iface(dbus_con) + + self.create_session(dbus_con) + self.get_zones(dbus_con) # The default zone is 1920x1080 barriers = [ @@ -396,41 +434,38 @@ def test_enable_disable(self, portal_mock): ), }, ] - self.set_pointer_barriers(portal_mock, barriers) - self.connect_to_eis( - portal_mock, - ) + self.set_pointer_barriers(dbus_con, barriers) + self.connect_to_eis(dbus_con) # Disable before enable should be a noop - self.disable( - portal_mock, - ) - method_calls = portal_mock.mock_interface.GetMethodCalls("Disable") + self.disable(dbus_con) + + method_calls = mock_intf.GetMethodCalls("Disable") assert len(method_calls) == 1 - self.enable( - portal_mock, - ) - method_calls = portal_mock.mock_interface.GetMethodCalls("Enable") + self.enable(dbus_con) + method_calls = mock_intf.GetMethodCalls("Enable") assert len(method_calls) == 1 - self.disable( - portal_mock, - ) - method_calls = portal_mock.mock_interface.GetMethodCalls("Disable") + self.disable(dbus_con) + method_calls = mock_intf.GetMethodCalls("Disable") assert len(method_calls) == 2 @pytest.mark.parametrize( - "params", + "template_params", ( { - "disable-delay": 200, + "inputcapture": { + "disable-delay": 200, + }, }, ), ) - def test_disable_signal(self, portal_mock): - self.create_session(portal_mock) - self.get_zones(portal_mock) + def test_disable_signal(self, portals, dbus_con): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + + self.create_session(dbus_con) + self.get_zones(dbus_con) # The default zone is 1920x1080 barriers = [ { @@ -440,8 +475,8 @@ def test_disable_signal(self, portal_mock): ), }, ] - self.set_pointer_barriers(portal_mock, barriers) - self.connect_to_eis(portal_mock) + self.set_pointer_barriers(dbus_con, barriers) + self.connect_to_eis(dbus_con) disabled_signal_received = False @@ -450,29 +485,26 @@ def cb_disabled(session_handle, options): disabled_signal_received = True assert session_handle == session_handle - inputcapture_intf = portal_mock.get_dbus_interface() inputcapture_intf.connect_to_signal("Disabled", cb_disabled) - - self.enable(portal_mock) - - mainloop = GLib.MainLoop() - GLib.timeout_add(500, mainloop.quit) - mainloop.run() - - assert disabled_signal_received + self.enable(dbus_con) + xdp.wait_for(lambda: disabled_signal_received) @pytest.mark.parametrize( - "params", + "template_params", ( { - "activated-delay": 200, - "deactivated-delay": 300, + "inputcapture": { + "activated-delay": 200, + "deactivated-delay": 300, + }, }, ), ) - def test_activated_signal(self, portal_mock): - self.create_session(portal_mock) - self.get_zones(portal_mock) + def test_activated_signal(self, portals, dbus_con): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + + self.create_session(dbus_con) + self.get_zones(dbus_con) # The default zone is 1920x1080 barriers = [ { @@ -482,8 +514,8 @@ def test_activated_signal(self, portal_mock): ), }, ] - self.set_pointer_barriers(portal_mock, barriers) - self.connect_to_eis(portal_mock) + self.set_pointer_barriers(dbus_con, barriers) + self.connect_to_eis(dbus_con) disabled_signal_received = False activated_signal_received = False @@ -517,43 +549,74 @@ def cb_deactivated(session_handle, options): 20.0, ) # template uses x+10, y+20 of first barrier - inputcapture_intf = portal_mock.get_dbus_interface() inputcapture_intf.connect_to_signal("Activated", cb_activated) inputcapture_intf.connect_to_signal("Deactivated", cb_deactivated) inputcapture_intf.connect_to_signal("Disabled", cb_disabled) - self.enable(portal_mock) + self.enable(dbus_con) - mainloop = GLib.MainLoop() - GLib.timeout_add(500, mainloop.quit) - mainloop.run() - - assert activated_signal_received - assert deactivated_signal_received + xdp.wait_for(lambda: activated_signal_received and deactivated_signal_received) assert not disabled_signal_received # Disabling should not trigger the signal - self.disable(portal_mock) + self.disable(dbus_con) + assert not disabled_signal_received - mainloop = GLib.MainLoop() - GLib.timeout_add(500, mainloop.quit) - mainloop.run() + @pytest.mark.parametrize( + "template_params", + ( + { + "inputcapture": { + "zones-changed-delay": 200, + }, + }, + ), + ) + def test_zones_changed_signal(self, portals, dbus_con): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") - assert not disabled_signal_received + self.create_session(dbus_con) + self.get_zones(dbus_con) + # The default zone is 1920x1080 + barriers = [ + { + "barrier_id": dbus.UInt32(10, variant_level=1), + "position": dbus.Struct( + [0, 0, 1920, 0], signature="iiii", variant_level=1 + ), + }, + ] + self.set_pointer_barriers(dbus_con, barriers) + self.connect_to_eis(dbus_con) + + zones_changed_signal_received = False + + def cb_zones_changed(session_handle, options): + nonlocal zones_changed_signal_received + zones_changed_signal_received = True + assert session_handle == session_handle + + inputcapture_intf.connect_to_signal("ZonesChanged", cb_zones_changed) + self.enable(dbus_con) + xdp.wait_for(lambda: zones_changed_signal_received) @pytest.mark.parametrize( - "params", + "template_params", ( { - "activated-delay": 200, - "deactivated-delay": 1000, - "disabled-delay": 1200, + "inputcapture": { + "activated-delay": 200, + "deactivated-delay": 1000, + "disabled-delay": 1200, + }, }, ), ) - def test_release(self, portal_mock): - self.create_session(portal_mock) - self.get_zones(portal_mock) + def test_release(self, portals, dbus_con): + inputcapture_intf = xdp.get_portal_iface(dbus_con, "InputCapture") + + self.create_session(dbus_con) + self.get_zones(dbus_con) # The default zone is 1920x1080 barriers = [ { @@ -563,8 +626,8 @@ def test_release(self, portal_mock): ), }, ] - self.set_pointer_barriers(portal_mock, barriers) - self.connect_to_eis(portal_mock) + self.set_pointer_barriers(dbus_con, barriers) + self.connect_to_eis(dbus_con) disabled_signal_received = False activated_signal_received = False @@ -584,24 +647,19 @@ def cb_deactivated(session_handle, options): nonlocal deactivated_signal_received deactivated_signal_received = True - inputcapture_intf = portal_mock.get_dbus_interface() inputcapture_intf.connect_to_signal("Disabled", cb_activated) inputcapture_intf.connect_to_signal("Activated", cb_activated) inputcapture_intf.connect_to_signal("Deactivated", cb_deactivated) - self.enable(portal_mock) - - mainloop = GLib.MainLoop() - GLib.timeout_add(300, mainloop.quit) - mainloop.run() + self.enable(dbus_con) - assert activated_signal_received + xdp.wait_for(lambda: activated_signal_received) assert activation_id is not None assert not deactivated_signal_received assert not disabled_signal_received self.release( - portal_mock, cursor_position=(10.0, 50.0), activation_id=activation_id + dbus_con, cursor_position=(10.0, 50.0), activation_id=activation_id ) # XDP should filter any signals the implementation may diff --git a/tests/test_location.py b/tests/test_location.py new file mode 100644 index 0000000..e7ee985 --- /dev/null +++ b/tests/test_location.py @@ -0,0 +1,125 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import tests as xdp + +import pytest +import dbus + + +@pytest.fixture +def required_templates(): + return {"geoclue2": {}} + + +class TestLocation: + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Location", 1) + + def get_geoclue_mock(self, dbus_con_sys): + geoclue_manager_proxy = dbus_con_sys.get_object( + "org.freedesktop.GeoClue2", + "/org/freedesktop/GeoClue2/Manager", + ) + geoclue_manager = dbus.Interface( + geoclue_manager_proxy, "org.freedesktop.GeoClue2.Manager" + ) + geoclue_client_proxy = dbus_con_sys.get_object( + "org.freedesktop.GeoClue2", geoclue_manager.GetClient() + ) + geoclue_mock = dbus.Interface( + geoclue_client_proxy, "org.freedesktop.GeoClue2.Mock" + ) + return geoclue_mock + + @pytest.mark.parametrize("required_templates", ({},)) + def test_no_geoclue(self, portals, dbus_con): + location_intf = xdp.get_portal_iface(dbus_con, "Location") + + session = xdp.Session( + dbus_con, + location_intf.CreateSession({"session_handle_token": "session_token0"}), + ) + + start_session_request = xdp.Request(dbus_con, location_intf) + start_session_response = start_session_request.call( + "Start", + session_handle=session.handle, + parent_window="window-hndl", + options={}, + ) + + assert start_session_response + assert start_session_response.response == 2 + + def test_session_update(self, portals, dbus_con, dbus_con_sys): + location_intf = xdp.get_portal_iface(dbus_con, "Location") + geoclue_mock_intf = self.get_geoclue_mock(dbus_con_sys) + + location_updated = False + updated_count = 0 + + session = xdp.Session( + dbus_con, + location_intf.CreateSession({"session_handle_token": "session_token0"}), + ) + + def cb_location_updated(session_handle, location): + nonlocal location_updated + nonlocal updated_count + + if updated_count == 0: + assert location["Latitude"] == 0 + assert location["Longitude"] == 0 + assert location["Accuracy"] == 0 + elif updated_count == 1: + assert location["Latitude"] == 11 + assert location["Longitude"] == 22 + assert location["Accuracy"] == 3 + + updated_count += 1 + location_updated = True + + location_intf.connect_to_signal("LocationUpdated", cb_location_updated) + + start_session_request = xdp.Request(dbus_con, location_intf) + start_session_response = start_session_request.call( + "Start", + session_handle=session.handle, + parent_window="window-hndl", + options={}, + ) + + assert start_session_response + assert start_session_response.response == 0 + + xdp.wait_for(lambda: location_updated) + location_updated = False + + assert updated_count == 1 + + geoclue_mock_intf.ChangeLocation( + { + "Latitude": dbus.UInt32(11), + "Longitude": dbus.UInt32(22), + "Accuracy": dbus.UInt32(3), + } + ) + + xdp.wait_for(lambda: location_updated) + location_updated = False + + assert updated_count == 2 + + def test_bad_accuracy(self, portals, dbus_con): + location_intf = xdp.get_portal_iface(dbus_con, "Location") + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + location_intf.CreateSession( + { + "session_handle_token": "session_token0", + "accuracy": dbus.UInt32(22), + } + ) + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) diff --git a/tests/test_notification.py b/tests/test_notification.py new file mode 100644 index 0000000..513ac8d --- /dev/null +++ b/tests/test_notification.py @@ -0,0 +1,531 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import pytest +import tempfile +import os +from pathlib import Path +from gi.repository import GLib, Gio + +SVG_IMAGE_DATA = """ + +""" + +SOUND_DATA = ( + b"\x52\x49\x46\x46\x24\x00\x00\x00\x57\x41\x56\x45" + + b"\x66\x6d\x74\x20\x10\x00\x00\x00\x01\x00\x01\x00" + + b"\x44\xac\x00\x00\x88\x58\x01\x00\x02\x00\x10\x00" + + b"\x64\x61\x74\x61\x00\x00\x00\x00" +) # fmt: skip + + +SUPPORTED_OPTIONS = { + "foo": "bar", +} + +NOTIFICATION_BASIC = { + "title": GLib.Variant("s", "title"), + "body": GLib.Variant("s", "test notification body"), + "priority": GLib.Variant("s", "normal"), + "default-action": GLib.Variant("s", "test-action"), +} + +NOTIFICATION_BUTTONS = { + "title": GLib.Variant("s", "test notification 2"), + "body": GLib.Variant("s", "test notification body 2"), + "priority": GLib.Variant("s", "low"), + "default-action": GLib.Variant("s", "test-action"), + "buttons": GLib.Variant( + "aa{sv}", + [ + { + "label": GLib.Variant("s", "button1"), + "action": GLib.Variant("s", "action1"), + }, + { + "label": GLib.Variant("s", "button2"), + "action": GLib.Variant("s", "action2"), + }, + ], + ), +} + + +@pytest.fixture +def required_templates(): + return { + "notification": { + "SupportedOptions": SUPPORTED_OPTIONS, + }, + } + + +class NotificationPortal(xdp.GDBusIface): + def __init__(self): + super().__init__( + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Notification", + ) + + def AddNotification(self, id, notification, fds=[]): + return self._call( + "AddNotification", + GLib.Variant("(sa{sv})", (id, notification)), + fds, + ) + + def RemoveNotification(self, id): + return self._call( + "RemoveNotification", + GLib.Variant("(s)", (id,)), + ) + + +class TestNotification: + def check_notification( + self, dbus_con, app_id, id, notification_in, notification_expected + ): + notification_intf = NotificationPortal() + mock_intf = xdp.get_mock_iface(dbus_con) + + method_calls = mock_intf.GetMethodCalls("AddNotification") + backend_calls = len(method_calls) + + notification_intf.AddNotification(id, notification_in) + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("AddNotification") + assert len(method_calls) == backend_calls + 1 + _, args = method_calls[-1] + assert args[0] == app_id + assert args[1] == id + + mock_notification = args[2] + assert ( + mock_notification == GLib.Variant("a{sv}", notification_expected).unpack() + ) + + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Notification", 2) + + def test_basic(self, portals, dbus_con, app_id): + self.check_notification( + dbus_con, + app_id, + "test1", + NOTIFICATION_BASIC, + NOTIFICATION_BASIC, + ) + + def test_remove(self, portals, dbus_con, app_id): + notification_intf = NotificationPortal() + mock_intf = xdp.get_mock_iface(dbus_con) + + id = "test1" + + notification_intf.AddNotification(id, NOTIFICATION_BASIC) + method_calls = mock_intf.GetMethodCalls("AddNotification") + assert len(method_calls) == 1 + _, args = method_calls[-1] + assert args[0] == app_id + assert args[1] == id + + notification_intf.RemoveNotification(id) + method_calls = mock_intf.GetMethodCalls("RemoveNotification") + assert len(method_calls) == 1 + _, args = method_calls[-1] + assert args[0] == app_id + assert args[1] == id + + def test_buttons(self, portals, dbus_con, app_id): + self.check_notification( + dbus_con, + app_id, + "test1", + NOTIFICATION_BUTTONS, + NOTIFICATION_BUTTONS, + ) + + def test_markup(self, portals, dbus_con, app_id): + bodies = [ + ( + "test notification body italic", + "test notification body italic", + ), + ( + 'test Some link', + 'test Some link', + ), + ( + "<html>", + "<html>", + ), + ( + '', + '', + ), + ( + "test \n newline \n\n some more space \n with trailing space ", + "test newline some more space with trailing space", + ), + ( + "test tag ", + "test tag", + ), + ( + "test notification body", + False, + ), + ( + "foobar", + False, + ), + ( + "test notification body", + False, + ), + ] + + i = 0 + for body_in, body_expected in bodies: + notification_in = NOTIFICATION_BASIC.copy() + notification_in["markup-body"] = GLib.Variant("s", body_in) + + notification_expected = NOTIFICATION_BASIC.copy() + if body_expected: + notification_expected["markup-body"] = GLib.Variant("s", body_expected) + + try: + self.check_notification( + dbus_con, + app_id, + f"test{i}", + notification_in, + notification_expected, + ) + assert body_expected + except GLib.GError as e: + assert "invalid markup-body" in e.message + + i += 1 + + def test_bad_arg(self, portals, dbus_con, app_id): + notification = NOTIFICATION_BASIC.copy() + notification["bodx"] = GLib.Variant("s", "Xtest") + + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + NOTIFICATION_BASIC, + ) + + def test_bad_priority(self, portals, dbus_con, app_id): + notification = NOTIFICATION_BASIC.copy() + notification["priority"] = GLib.Variant("s", "invalid") + + try: + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "invalid not a priority" in e.message + + def test_bad_button(self, portals, dbus_con, app_id): + notification = NOTIFICATION_BUTTONS.copy() + notification["buttons"] = GLib.Variant( + "aa{sv}", + [ + { + "labex": GLib.Variant("s", "button1"), + "action": GLib.Variant("s", "action1"), + }, + ], + ) + + try: + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "invalid button" in e.message + + def test_display_hint(self, portals, dbus_con, app_id): + notification = NOTIFICATION_BASIC.copy() + notification["display-hint"] = GLib.Variant( + "as", + [ + "transient", + "show-as-new", + ], + ) + + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + + notification = NOTIFICATION_BASIC.copy() + notification["display-hint"] = GLib.Variant( + "as", + [ + "unsupported-hint", + ], + ) + + try: + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "not a display-hint" in e.message + + def test_category(self, portals, dbus_con, app_id): + notification = NOTIFICATION_BASIC.copy() + notification["category"] = GLib.Variant("s", "im.received") + + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + + notification = NOTIFICATION_BASIC.copy() + notification["category"] = GLib.Variant("s", "x-vendor.custom") + + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + + notification = NOTIFICATION_BASIC.copy() + notification["category"] = GLib.Variant("s", "unsupported-type") + + try: + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "not a supported category" in e.message + + def test_supported_options(self, portals, dbus_con, app_id): + properties_intf = xdp.get_iface(dbus_con, "org.freedesktop.DBus.Properties") + + options = properties_intf.Get( + "org.freedesktop.portal.Notification", "SupportedOptions" + ) + + assert options == SUPPORTED_OPTIONS + + def test_icon_themed(self, portals, dbus_con, app_id): + notification_intf = NotificationPortal() + icon = Gio.ThemedIcon.new("test-icon-symbolic") + + notification = NOTIFICATION_BASIC.copy() + notification["icon"] = icon.serialize() + + notification_intf.AddNotification("test1", notification) + + def test_icon_bytes(self, portals, dbus_con, app_id): + notification_intf = NotificationPortal() + bytes = GLib.Bytes.new(SVG_IMAGE_DATA.encode("utf-8")) + icon = Gio.BytesIcon.new(bytes) + + notification = NOTIFICATION_BASIC.copy() + notification["icon"] = icon.serialize() + + notification_intf.AddNotification("test1", notification) + + def test_icon_file(self, portals, dbus_con, app_id): + notification_intf = NotificationPortal() + fd, file_path = tempfile.mkstemp(prefix="notification_icon_", dir=Path.home()) + os.write(fd, SVG_IMAGE_DATA.encode("utf-8")) + + file = Gio.File.new_for_path(file_path) + icon = Gio.FileIcon.new(file) + + notification = NOTIFICATION_BASIC.copy() + notification["icon"] = icon.serialize() + + notification = { + "title": GLib.Variant("s", "title"), + "icon": icon.serialize(), + } + + notification_intf.AddNotification("test1", notification) + + def test_icon_bad(self, portals, dbus_con, app_id): + notification_intf = NotificationPortal() + + notification = NOTIFICATION_BASIC.copy() + + bad_icons = [ + GLib.Variant("(sv)", ["themed", GLib.Variant("s", "test-icon-symbolic")]), + GLib.Variant( + "(sv)", + ["bytes", GLib.Variant("as", ["test-icon-symbolic", "test-icon"])], + ), + GLib.Variant("(sv)", ["file-descriptor", GLib.Variant("s", "")]), + GLib.Variant("(sv)", ["file-descriptor", GLib.Variant("h", 0)]), + ] + + for icon in bad_icons: + notification["icon"] = icon + try: + notification_intf.AddNotification("test1", notification) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.DBUS_ERROR) + + def test_sound_simple(self, portals, dbus_con, app_id): + notification = NOTIFICATION_BASIC.copy() + notification["sound"] = GLib.Variant("s", "default") + + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + + notification = NOTIFICATION_BASIC.copy() + notification["sound"] = GLib.Variant("s", "silent") + + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + + notification = NOTIFICATION_BASIC.copy() + notification["sound"] = GLib.Variant("s", "bad") + + try: + self.check_notification( + dbus_con, + app_id, + "test1", + notification, + notification, + ) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "invalid sound: invalid option" in e.message + + def test_sound_file(self, portals, dbus_con, app_id): + notification_intf = NotificationPortal() + mock_intf = xdp.get_mock_iface(dbus_con) + + fd, file_path = tempfile.mkstemp(prefix="notification_sound_", dir=Path.home()) + os.write(fd, SOUND_DATA) + + file = Gio.File.new_for_path(file_path) + + notification = NOTIFICATION_BASIC.copy() + notification["sound"] = GLib.Variant( + "(sv)", + ( + "file", + GLib.Variant("s", file.get_uri()), + ), + ) + + notification_intf.AddNotification("test1", notification) + + method_calls = mock_intf.GetMethodCalls("AddNotification") + assert len(method_calls) == 1 + _, args = method_calls[-1] + mock_notification = args[2] + + assert "sound" not in mock_notification + + def test_sound_fd(self, portals, dbus_con, app_id): + notification_intf = NotificationPortal() + mock_intf = xdp.get_mock_iface(dbus_con) + + fd = os.memfd_create("notification_sound_test", os.MFD_ALLOW_SEALING) + os.write(fd, SOUND_DATA) + + notification = NOTIFICATION_BASIC.copy() + notification["sound"] = GLib.Variant( + "(sv)", + ( + "file-descriptor", + GLib.Variant("h", 0), + ), + ) + + notification_intf.AddNotification("test1", notification, [fd]) + + method_calls = mock_intf.GetMethodCalls("AddNotification") + assert len(method_calls) == 1 + _, args = method_calls[-1] + mock_notification = args[2] + + assert mock_notification["sound"][0] == "file-descriptor" + mock_fd = mock_notification["sound"][1] + mock_fd = mock_fd.take() + + os.lseek(fd, 0, os.SEEK_SET) + fd_contents = os.read(mock_fd, 1000) + assert fd_contents == SOUND_DATA + + os.close(mock_fd) + os.close(fd) + + def test_sound_bad(self, portals, dbus_con, app_id): + notification_intf = NotificationPortal() + + notification = NOTIFICATION_BASIC.copy() + + bad_sounds = [ + # bad type + GLib.Variant("(sv)", ["file-descriptor", GLib.Variant("s", "")]), + # not sending the FD for the handle + GLib.Variant("(sv)", ["file-descriptor", GLib.Variant("h", 13)]), + ] + + for sound in bad_sounds: + notification["sound"] = sound + try: + notification_intf.AddNotification("test1", notification) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.DBUS_ERROR) + pass diff --git a/tests/test_openuri.py b/tests/test_openuri.py new file mode 100644 index 0000000..6265059 --- /dev/null +++ b/tests/test_openuri.py @@ -0,0 +1,352 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import dbus +import pytest +import os +import tempfile +from pathlib import Path +from typing import Any + + +defaults_list = b"""[Default Applications] +x-scheme-handler/http=furrfix.desktop; +text/plain=furrfix.desktop +""" + +furrfix_desktop = b"""[Desktop Entry] +Version=1.0 +Name=Furrfix +GenericName=Not a Web Browser +Comment=Don't Browse the Web +Exec=true %u +Icon=furrfix +Terminal=false +Type=Application +MimeType=text/plain;text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/xdg-desktop-portal-test; +StartupNotify=true +Categories=Network;WebBrowser; +Keywords=web;browser;internet; +""" + +mimeinfo_cache = b"""[MIME Cache] +application/vnd.mozilla.xul+xml=furrfix.desktop; +application/xhtml+xml=furrfix.desktop; +text/plain=furrfix.desktop; +text/html=furrfix.desktop; +text/mml=furrfix.desktop; +text/xml=furrfix.desktop; +x-scheme-handler/http=furrfix.desktop; +x-scheme-handler/https=furrfix.desktop; +x-scheme-handler/xdg-desktop-portal-test=furrfix.desktop; +""" + + +@pytest.fixture +def xdg_data_home_files(): + return { + "applications/defaults.list": defaults_list, + "applications/furrfix.desktop": furrfix_desktop, + "applications/mimeinfo.cache": mimeinfo_cache, + } + + +@pytest.fixture +def required_templates(): + return { + "appchooser": {}, + "lockdown": {}, + } + + +class TestOpenURI: + def set_permissions(self, dbus_con, type, permissions): + perm_store_intf = xdp.get_permission_store_iface(dbus_con) + perm_store_intf.SetPermission( + "desktop-used-apps", + True, + "inhibit", + type, + permissions, + ) + + def enable_paranoid_mode(self, dbus_con, type): + # turn on paranoid mode to ensure we get a backend call + perm_store_intf = xdp.get_permission_store_iface(dbus_con) + perm_store_intf.SetValue( + "desktop-used-apps", + True, + type, + dbus.Dictionary( + { + "always-ask": True, + }, + signature="sv", + ), + ) + + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "OpenURI", 5) + + def test_http1(self, portals, dbus_con, app_id): + openuri_intf = xdp.get_portal_iface(dbus_con, "OpenURI") + mock_intf = xdp.get_mock_iface(dbus_con) + + scheme_handler = "x-scheme-handler/http" + self.enable_paranoid_mode(dbus_con, scheme_handler) + + uri = "http://www.flatpak.org" + writable = False + activation_token = "token" + + request = xdp.Request(dbus_con, openuri_intf) + options = { + "writable": writable, + "activation_token": activation_token, + } + response = request.call( + "OpenURI", + parent_window="", + uri=uri, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("ChooseApplication") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert "furrfix" in args[3] + assert args[4]["uri"] == uri + assert args[4]["content_type"] == scheme_handler + assert args[4]["activation_token"] == activation_token + + def test_http2(self, portals, dbus_con): + openuri_intf = xdp.get_portal_iface(dbus_con, "OpenURI") + mock_intf = xdp.get_mock_iface(dbus_con) + + scheme_handler = "x-scheme-handler/http" + self.set_permissions(dbus_con, scheme_handler, ["furrfix", "3", "3"]) + + uri = "http://www.flatpak.org" + writable = False + activation_token = "token" + + request = xdp.Request(dbus_con, openuri_intf) + options = { + "writable": writable, + "activation_token": activation_token, + } + response = request.call( + "OpenURI", + parent_window="", + uri=uri, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was not called because the choice thresold + # has been reached + method_calls = mock_intf.GetMethodCalls("ChooseApplication") + assert len(method_calls) == 0 + + def test_file(self, portals, dbus_con, app_id): + openuri_intf = xdp.get_portal_iface(dbus_con, "OpenURI") + mock_intf = xdp.get_mock_iface(dbus_con) + + scheme_handler = "text/plain" + self.enable_paranoid_mode(dbus_con, scheme_handler) + + fd, _ = tempfile.mkstemp(prefix="openuri_mock_file_", dir=Path.home()) + os.write(fd, b"openuri_mock_file") + + writable = False + activation_token = "token" + + request = xdp.Request(dbus_con, openuri_intf) + options = { + "writable": writable, + "activation_token": activation_token, + } + response = request.call( + "OpenFile", + parent_window="", + fd=fd, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("ChooseApplication") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert "furrfix" in args[3] + assert args[4]["content_type"] == scheme_handler + assert args[4]["activation_token"] == activation_token + + path = args[4]["uri"] + assert path.startswith("file:///") + + with open(path[7:]) as file: + openuri_file_contents = file.read() + assert openuri_file_contents == "openuri_mock_file" + + @pytest.mark.parametrize("template_params", ({"appchooser": {"response": 1}},)) + def test_cancel(self, portals, dbus_con): + openuri_intf = xdp.get_portal_iface(dbus_con, "OpenURI") + + scheme_handler = "x-scheme-handler/http" + self.enable_paranoid_mode(dbus_con, scheme_handler) + + uri = "http://www.flatpak.org" + + request = xdp.Request(dbus_con, openuri_intf) + options: Any = {} + response = request.call( + "OpenURI", + parent_window="", + uri=uri, + options=options, + ) + + assert response + assert response.response == 1 + + @pytest.mark.parametrize( + "template_params", ({"appchooser": {"expect-close": True}},) + ) + def test_close(self, portals, dbus_con, app_id): + openuri_intf = xdp.get_portal_iface(dbus_con, "OpenURI") + mock_intf = xdp.get_mock_iface(dbus_con) + + scheme_handler = "x-scheme-handler/http" + self.enable_paranoid_mode(dbus_con, scheme_handler) + + uri = "http://www.flatpak.org" + writable = False + activation_token = "token" + + request = xdp.Request(dbus_con, openuri_intf) + request.schedule_close(1000) + options = { + "writable": writable, + "activation_token": activation_token, + } + request.call( + "OpenURI", + parent_window="", + uri=uri, + options=options, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("ChooseApplication") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert "furrfix" in args[3] + assert args[4]["uri"] == uri + assert args[4]["content_type"] == scheme_handler + assert args[4]["activation_token"] == activation_token + + @pytest.mark.parametrize( + "template_params", ({"lockdown": {"disable-application-handlers": True}},) + ) + def test_lockdown(self, portals, dbus_con, app_id): + openuri_intf = xdp.get_portal_iface(dbus_con, "OpenURI") + + scheme_handler = "x-scheme-handler/http" + self.enable_paranoid_mode(dbus_con, scheme_handler) + + uri = "http://www.flatpak.org" + writable = False + activation_token = "token" + + request = xdp.Request(dbus_con, openuri_intf) + options = { + "writable": writable, + "activation_token": activation_token, + } + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "OpenURI", + parent_window="", + uri=uri, + options=options, + ) + assert ( + excinfo.value.get_dbus_name() == "org.freedesktop.portal.Error.NotAllowed" + ) + + def test_dir(self, portals, dbus_con, app_id): + openuri_intf = xdp.get_portal_iface(dbus_con, "OpenURI") + mock_intf = xdp.get_mock_iface(dbus_con) + + scheme_handler = "inode/directory" + self.enable_paranoid_mode(dbus_con, scheme_handler) + + fd, file_path = tempfile.mkstemp(prefix="openuri_mock_file_", dir=Path.home()) + os.write(fd, b"openuri_mock_file") + + activation_token = "token" + + request = xdp.Request(dbus_con, openuri_intf) + options = { + "activation_token": activation_token, + } + response = request.call( + "OpenDirectory", + parent_window="", + fd=fd, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the appchooser portal got called to open the containing dir + method_calls = mock_intf.GetMethodCalls("ChooseApplication") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[4]["content_type"] == scheme_handler + assert args[4]["activation_token"] == activation_token + + path = args[4]["uri"] + assert path.startswith("file:///") + + assert Path(path[7:]) == Path(file_path).parent + + def test_scheme_supported(self, portals, dbus_con): + openuri_intf = xdp.get_portal_iface(dbus_con, "OpenURI") + + supported = openuri_intf.SchemeSupported("https", {}) + assert supported + + supported = openuri_intf.SchemeSupported("bogusnonexistanthandler", {}) + assert not supported + + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + openuri_intf.SchemeSupported("", {}) + assert ( + excinfo.value.get_dbus_name() + == "org.freedesktop.portal.Error.InvalidArgument" + ) diff --git a/tests/test_permission_store.py b/tests/test_permission_store.py new file mode 100644 index 0000000..715c01b --- /dev/null +++ b/tests/test_permission_store.py @@ -0,0 +1,298 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import dbus +from gi.repository import GLib, Gio + + +class PermissionStore(xdp.GDBusIface): + def __init__(self): + super().__init__( + "org.freedesktop.impl.portal.PermissionStore", + "/org/freedesktop/impl/portal/PermissionStore", + "org.freedesktop.impl.portal.PermissionStore", + ) + + def Lookup(self, table, id): + return self._call( + "Lookup", + GLib.Variant("(ss)", (table, id)), + ) + + def Set(self, table, create, id, perm, data): + return self._call( + "Set", + GLib.Variant("(sbsa{sas}v)", (table, create, id, perm, data)), + ) + + def SetValue(self, table, create, id, data): + return self._call( + "SetValue", + GLib.Variant("(sbsv)", (table, create, id, data)), + ) + + def SetPermission(self, table, create, id, app, perm): + return self._call( + "SetPermission", + GLib.Variant("(sbssas)", (table, create, id, app, perm)), + ) + + def SetPermissionAsync(self, table, create, id, app, perm, user_cb): + self._call_async( + "SetPermission", + GLib.Variant("(sbssas)", (table, create, id, app, perm)), + cb=user_cb, + ) + + def DeletePermissionAsync(self, table, id, app, user_cb): + self._call_async( + "DeletePermission", + GLib.Variant("(sss)", (table, id, app)), + cb=user_cb, + ) + + def DeleteAsync(self, table, id, user_cb): + self._call_async( + "Delete", + GLib.Variant("(ss)", (table, id)), + cb=user_cb, + ) + + def Delete(self, table, id): + return self._call( + "Delete", + GLib.Variant("(ss)", (table, id)), + ) + + def GetPermission(self, table, id, app): + return self._call( + "GetPermission", + GLib.Variant("(sss)", (table, id, app)), + ) + + +class TestPermissionStore: + def test_version(self, portals, dbus_con): + permission_store = dbus_con.get_object( + "org.freedesktop.impl.portal.PermissionStore", + "/org/freedesktop/impl/portal/PermissionStore", + ) + + properties_intf = dbus.Interface( + permission_store, + "org.freedesktop.DBus.Properties", + ) + portal_version = properties_intf.Get( + "org.freedesktop.impl.portal.PermissionStore", + "version", + ) + assert int(portal_version) == 2 + + def test_delete_race(self, portals, dbus_con): + permission_store_intf = PermissionStore() + finished_count = 0 + + table = "inhibit" + id = "inhibit" + perms = ["logout", "suspend"] + + def cb(_): + nonlocal finished_count + + finished_count += 1 + + permission_store_intf.SetPermissionAsync(table, True, id, "a", perms, cb) + permission_store_intf.DeleteAsync(table, id, cb) + + xdp.wait_for(lambda: finished_count >= 2) + + try: + permission_store_intf.Lookup(table, id) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "org.freedesktop.portal.Error.NotFound" in e.message + assert e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.DBUS_ERROR) + + permission_store_intf.SetPermissionAsync(table, True, id, "a", perms, cb) + permission_store_intf.SetPermissionAsync(table, True, id, "b", perms, cb) + permission_store_intf.DeletePermissionAsync(table, id, "a", cb) + + xdp.wait_for(lambda: finished_count >= 4) + + result, _ = permission_store_intf.Lookup(table, id) + perms_out = result.unpack()[0] + assert perms_out == {"b": perms} + + permission_store_intf.SetPermissionAsync(table, True, id, "a", perms, cb) + permission_store_intf.DeletePermissionAsync(table, id, "b", cb) + permission_store_intf.DeletePermissionAsync(table, id, "a", cb) + + xdp.wait_for(lambda: finished_count >= 7) + + result, _ = permission_store_intf.Lookup(table, id) + perms_out = result.unpack()[0] + assert perms_out == {} + + def test_change(self, portals, dbus_con): + permission_store_intf = PermissionStore() + changed_count = 0 + + table = "TEST" + id = "test-resource" + app = "one.two.three" + perms = ["one", "two"] + + def cb_changed1(results): + nonlocal changed_count + + cb_table, cb_id, deleted, _, cb_perms = results.unpack() + + assert cb_table == table + assert cb_id == id + assert not deleted + assert cb_perms[app] == perms + + changed_count += 1 + + cs = permission_store_intf.connect_to_signal("Changed", cb_changed1) + permission_store_intf.SetPermissionAsync(table, True, id, app, perms, None) + xdp.wait_for(lambda: changed_count >= 1) + cs.disconnect() + + def cb_changed2(results): + nonlocal changed_count + + cb_table, cb_id, deleted, _, _ = results.unpack() + + assert cb_table == table + assert cb_id == id + assert deleted + + changed_count += 1 + + cs = permission_store_intf.connect_to_signal("Changed", cb_changed2) + permission_store_intf.Delete(table, id) + xdp.wait_for(lambda: changed_count >= 2) + cs.disconnect() + + def test_lookup(self, portals, dbus_con): + permission_store_intf = PermissionStore() + + table = "TEST" + id = "test-resource" + perms = ["one", "two"] + data = True + + try: + permission_store_intf.Lookup(table, id) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "org.freedesktop.portal.Error.NotFound" in e.message + + permissions = [(id, perms)] + permission_store_intf.Set(table, True, id, permissions, GLib.Variant("b", data)) + + result, _ = permission_store_intf.Lookup(table, id) + perms_out = result.unpack()[0] + data_out = result.unpack()[1] + + assert id in perms_out + perms_out = perms_out[id] + assert perms_out == perms + + assert data_out == data + + def test_set_value(self, portals, dbus_con): + permission_store_intf = PermissionStore() + + table = "TEST" + id = "test-resource" + data = True + + try: + permission_store_intf.Lookup(table, id) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "org.freedesktop.portal.Error.NotFound" in e.message + + permission_store_intf.SetValue(table, True, id, GLib.Variant("b", data)) + + result, _ = permission_store_intf.Lookup(table, id) + perms_out = result.unpack()[0] + data_out = result.unpack()[1] + assert perms_out == {} + assert data_out == data + + def test_create(self, portals, dbus_con): + permission_store_intf = PermissionStore() + + table = "inhibit" + id = "inhibit" + app = "" + perms = ["logout", "suspend"] + + try: + permission_store_intf.SetPermission( + table, + # Do not create if it does not exist + False, + id, + app, + perms, + ) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "org.freedesktop.portal.Error.NotFound" in e.message + + permission_store_intf.SetPermission(table, True, id, app, perms) + + def test_delete(self, portals, dbus_con): + permission_store_intf = PermissionStore() + + table = "inhibit" + id = "inhibit" + app = "" + perms = ["logout", "suspend"] + + try: + permission_store_intf.Delete(table, id) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "org.freedesktop.portal.Error.NotFound" in e.message + + permission_store_intf.SetPermission(table, True, id, app, perms) + + permission_store_intf.Delete(table, id) + + try: + permission_store_intf.Lookup(table, id) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "org.freedesktop.portal.Error.NotFound" in e.message + + def test_get_permission(self, portals, dbus_con): + permission_store_intf = PermissionStore() + + table = "notifications" + id = "notification" + app = "a" + perms = ["yes"] + + try: + permission_store_intf.GetPermission(table, id, app) + assert False, "This statement should not be reached" + except GLib.GError as e: + assert "org.freedesktop.portal.Error.NotFound" in e.message + + permission_store_intf.SetPermission(table, True, id, app, perms) + + result, _ = permission_store_intf.GetPermission(table, id, app) + permissions = result.unpack()[0] + assert permissions == perms + + result, _ = permission_store_intf.GetPermission(table, id, "no-such-app") + permissions = result.unpack()[0] + assert permissions == [] diff --git a/tests/test_print.py b/tests/test_print.py new file mode 100644 index 0000000..f8bb4ab --- /dev/null +++ b/tests/test_print.py @@ -0,0 +1,345 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import dbus +import pytest +import os +import tempfile +from pathlib import Path +from typing import Any + + +PRINT_PREPARE_DATA = { + "token": dbus.UInt32(1337), +} + + +@pytest.fixture +def required_templates(): + return { + "print": { + "prepare-results": PRINT_PREPARE_DATA, + }, + "lockdown": {}, + } + + +class TestPrint: + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Print", 3) + + def test_prepare_print_basic(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test Title" + settings: Any = {} + page_setup: Any = {} + options = { + "modal": True, + "accept_label": "Accept", + "supported_output_file_formats": ["pdf"], + } + + request = xdp.Request(dbus_con, print_intf) + response = request.call( + "PreparePrint", + parent_window="", + title=title, + settings=settings, + page_setup=page_setup, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("PreparePrint") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + assert args[4] == settings + assert args[5] == page_setup + assert args[6]["modal"] == options["modal"] + assert args[6]["accept_label"] == options["accept_label"] + assert ( + args[6]["supported_output_file_formats"] + == options["supported_output_file_formats"] + ) + + @pytest.mark.parametrize("template_params", ({"print": {"response": 1}},)) + def test_prepare_print_cancel(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test Title" + + request = xdp.Request(dbus_con, print_intf) + response = request.call( + "PreparePrint", + parent_window="", + title=title, + settings={}, + page_setup={}, + options={}, + ) + + assert response + assert response.response == 1 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("PreparePrint") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + + @pytest.mark.parametrize("template_params", ({"print": {"expect-close": True}},)) + def test_prepare_print_close(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test Title" + + request = xdp.Request(dbus_con, print_intf) + request.schedule_close(1000) + request.call( + "PreparePrint", + parent_window="", + title=title, + settings={}, + page_setup={}, + options={}, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("PreparePrint") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + + @pytest.mark.parametrize( + "template_params", ({"lockdown": {"disable-printing": True}},) + ) + def test_prepare_print_lockdown(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test Title" + + request = xdp.Request(dbus_con, print_intf) + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "PreparePrint", + parent_window="", + title=title, + settings={}, + page_setup={}, + options={}, + ) + assert ( + excinfo.value.get_dbus_name() == "org.freedesktop.portal.Error.NotAllowed" + ) + + # Check the impl portal was not called + method_calls = mock_intf.GetMethodCalls("PreparePrint") + assert len(method_calls) == 0 + + def test_print_basic(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + fd, file_path = tempfile.mkstemp(prefix="print_mock_file_", dir=Path.home()) + os.write(fd, b"print_mock_file") + + title = "Test Title" + options = { + "modal": True, + "token": "token", + "supported_output_file_formats": ["svg"], + } + + request = xdp.Request(dbus_con, print_intf) + response = request.call( + "Print", + parent_window="", + title=title, + fd=fd, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Print") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == title + assert args[5]["modal"] == options["modal"] + assert ( + args[5]["supported_output_file_formats"] + == options["supported_output_file_formats"] + ) + + backend_fd = args[4].take() + ino = os.stat(f"/proc/self/fd/{fd}").st_ino + ino_backend = os.stat(f"/proc/self/fd/{backend_fd}").st_ino + os.close(fd) + assert ino == ino_backend + + @pytest.mark.parametrize("template_params", ({"print": {"response": 1}},)) + def test_print_cancel(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + fd, _ = tempfile.mkstemp(prefix="print_mock_file_", dir=Path.home()) + os.write(fd, b"print_mock_file") + + title = "Test Title" + + request = xdp.Request(dbus_con, print_intf) + response = request.call( + "Print", + parent_window="", + title=title, + fd=fd, + options={}, + ) + + assert response + assert response.response == 1 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Print") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + + @pytest.mark.parametrize("template_params", ({"print": {"expect-close": True}},)) + def test_print_close(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + fd, _ = tempfile.mkstemp(prefix="print_mock_file_", dir=Path.home()) + os.write(fd, b"print_mock_file") + + title = "Test Title" + + request = xdp.Request(dbus_con, print_intf) + request.schedule_close(1000) + request.call( + "Print", + parent_window="", + title=title, + fd=fd, + options={}, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Print") + assert len(method_calls) == 1 + _, args = method_calls.pop() + assert args[1] == app_id + + @pytest.mark.parametrize( + "template_params", ({"lockdown": {"disable-printing": True}},) + ) + def test_print_lockdown(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + fd, _ = tempfile.mkstemp(prefix="print_mock_file_", dir=Path.home()) + os.write(fd, b"print_mock_file") + + title = "Test Title" + + request = xdp.Request(dbus_con, print_intf) + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call( + "Print", + parent_window="", + title=title, + fd=fd, + options={}, + ) + assert ( + excinfo.value.get_dbus_name() == "org.freedesktop.portal.Error.NotAllowed" + ) + + # Check the impl portal was not called + method_calls = mock_intf.GetMethodCalls("Print") + assert len(method_calls) == 0 + + def test_print_prepare_and_print(self, portals, dbus_con, app_id): + print_intf = xdp.get_portal_iface(dbus_con, "Print") + mock_intf = xdp.get_mock_iface(dbus_con) + + title = "Test Title" + + fd, file_path = tempfile.mkstemp(prefix="print_mock_file_", dir=Path.home()) + os.write(fd, b"print_mock_file") + + request = xdp.Request(dbus_con, print_intf) + response = request.call( + "PreparePrint", + parent_window="", + title=title, + settings={}, + page_setup={}, + options={}, + ) + + assert response + assert response.response == 0 + assert response.results["token"] == PRINT_PREPARE_DATA["token"] + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("PreparePrint") + assert len(method_calls) == 1 + + options = { + "token": response.results["token"], + } + + request = xdp.Request(dbus_con, print_intf) + response = request.call( + "Print", + parent_window="", + title=title, + fd=fd, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Print") + assert len(method_calls) == 1 + _, args = method_calls.pop() + + assert args[5]["token"] == PRINT_PREPARE_DATA["token"] + + backend_fd = args[4].take() + ino = os.stat(f"/proc/self/fd/{fd}").st_ino + ino_backend = os.stat(f"/proc/self/fd/{backend_fd}").st_ino + os.close(fd) + assert ino == ino_backend diff --git a/tests/test_registry.py b/tests/test_registry.py new file mode 100644 index 0000000..64a0c4a --- /dev/null +++ b/tests/test_registry.py @@ -0,0 +1,142 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import tests as xdp + +import dbus +import pytest + + +@pytest.fixture +def app_id(): + return "org.example.WrongAppId" + + +@pytest.fixture +def required_templates(): + return {"remotedesktop": {}} + + +class TestRegistry: + def test_version(self, portals, dbus_con): + documents = dbus_con.get_object( + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + ) + + properties_intf = dbus.Interface( + documents, + "org.freedesktop.DBus.Properties", + ) + portal_version = properties_intf.Get( + "org.freedesktop.host.portal.Registry", + "version", + ) + assert int(portal_version) == 1 + + def create_dummy_session(self, dbus_con): + remotedesktop_intf = xdp.get_portal_iface(dbus_con, "RemoteDesktop") + request = xdp.Request(dbus_con, remotedesktop_intf) + + session_counter_attr_name = "session_counter" + session_counter = getattr(self, session_counter_attr_name, 0) + setattr(self, session_counter_attr_name, session_counter + 1) + + options = { + "session_handle_token": f"session_token{session_counter}", + } + response = request.call( + "CreateSession", + options=options, + ) + assert response + assert response.response == 0 + + return xdp.Session.from_response(dbus_con, response) + + def test_registerless(self, portals, dbus_con, app_id): + mock_intf = xdp.get_mock_iface(dbus_con) + + expected_app_id = app_id + + session = self.create_dummy_session(dbus_con) + + app_id = mock_intf.GetSessionAppId(session.handle) + assert app_id == expected_app_id + + def test_register(self, portals, dbus_con): + registry_intf = xdp.get_portal_iface(dbus_con, "Registry", domain="host") + mock_intf = xdp.get_mock_iface(dbus_con) + + expected_app_id = "org.example.CorrectAppId" + registry_intf.Register(expected_app_id, {}) + + session = self.create_dummy_session(dbus_con) + + app_id = mock_intf.GetSessionAppId(session.handle) + assert app_id == expected_app_id + + def test_late_register(self, portals, dbus_con, app_id): + registry_intf = xdp.get_portal_iface(dbus_con, "Registry", domain="host") + mock_intf = xdp.get_mock_iface(dbus_con) + + expected_app_id = app_id + unexpected_app_id = "org.example.CorrectAppId" + + session = self.create_dummy_session(dbus_con) + + app_id = mock_intf.GetSessionAppId(session.handle) + assert app_id == expected_app_id + + with pytest.raises(dbus.exceptions.DBusException) as exc_info: + registry_intf.Register(unexpected_app_id, {}) + exc_info.match(".*Connection already associated with an application ID.*") + + new_session = self.create_dummy_session(dbus_con) + + new_app_id = mock_intf.GetSessionAppId(new_session.handle) + assert new_app_id == expected_app_id + + def test_multiple_connections(self, portals, dbus_con, app_id): + registry_intf = xdp.get_portal_iface(dbus_con, "Registry", domain="host") + mock_intf = xdp.get_mock_iface(dbus_con) + + expected_app_id = "org.example.CorrectAppId" + unexpected_app_id = app_id + + registry_intf.Register(expected_app_id, {}) + session = self.create_dummy_session(dbus_con) + app_id = mock_intf.GetSessionAppId(session.handle) + assert app_id == expected_app_id + + dbus_con2 = dbus.bus.BusConnection(dbus.bus.BusConnection.TYPE_SESSION) + dbus_con2.set_exit_on_disconnect(False) + mock_intf2 = xdp.get_mock_iface(dbus_con2) + session2 = self.create_dummy_session(dbus_con2) + app_id2 = mock_intf2.GetSessionAppId(session2.handle) + assert app_id2 == unexpected_app_id + dbus_con2.close() + + dbus_con3 = dbus.bus.BusConnection(dbus.bus.BusConnection.TYPE_SESSION) + dbus_con3.set_exit_on_disconnect(False) + mock_intf3 = xdp.get_mock_iface(dbus_con3) + registry_intf3 = xdp.get_portal_iface(dbus_con3, "Registry", domain="host") + registry_intf3.Register(expected_app_id, {}) + session3 = self.create_dummy_session(dbus_con3) + app_id3 = mock_intf3.GetSessionAppId(session3.handle) + assert app_id3 == expected_app_id + dbus_con3.close() + + def test_no_reregister(self, portals, dbus_con): + registry_intf = xdp.get_portal_iface(dbus_con, "Registry", domain="host") + mock_intf = xdp.get_mock_iface(dbus_con) + + expected_app_id = "org.example.CorrectAppId" + + registry_intf.Register(expected_app_id, {}) + session = self.create_dummy_session(dbus_con) + app_id = mock_intf.GetSessionAppId(session.handle) + assert app_id == expected_app_id + + with pytest.raises(dbus.exceptions.DBusException) as exc_info: + registry_intf.Register(expected_app_id, {}) + exc_info.match(".*Connection already associated with an application ID.*") diff --git a/tests/test_remotedesktop.py b/tests/test_remotedesktop.py index 49b6b18..3627c34 100644 --- a/tests/test_remotedesktop.py +++ b/tests/test_remotedesktop.py @@ -2,26 +2,28 @@ # # This file is formatted with Python Black - -from tests import PortalMock, Session -from gi.repository import GLib +import tests as xdp import dbus import pytest import socket +from typing import List, Dict, Any @pytest.fixture -def portal_name(): - return "RemoteDesktop" +def required_templates(): + return {"remotedesktop": {}} class TestRemoteDesktop: - def test_version(self, portal_mock): - portal_mock.check_version(2) + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "RemoteDesktop", 2) + + def test_create_close_session(self, portals, dbus_con): + remotedesktop_intf = xdp.get_portal_iface(dbus_con, "RemoteDesktop") + mock_intf = xdp.get_mock_iface(dbus_con) - def test_remote_desktop_create_close_session(self, portal_mock): - request = portal_mock.create_request() + request = xdp.Request(dbus_con, remotedesktop_intf) options = { "session_handle_token": "session_token0", } @@ -30,27 +32,42 @@ def test_remote_desktop_create_close_session(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) + session = xdp.Session.from_response(dbus_con, response) # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[1] == session.handle - assert args[2] == "" # appid + # assert args[2] == "" # appid, not necessary empty session.close() + xdp.wait_for(lambda: session.closed) + + @pytest.mark.parametrize("token", ("Invalid-Token&", "", "/foo")) + def test_remote_desktop_create_session_invalid(self, portals, dbus_con, token): + remotedesktop_intf = xdp.get_portal_iface(dbus_con, "RemoteDesktop") + + request = xdp.Request(dbus_con, remotedesktop_intf) + options = {"session_handle_token": token} + + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + request.call("CreateSession", options=options) - mainloop = GLib.MainLoop() - GLib.timeout_add(2000, mainloop.quit) - mainloop.run() + e = excinfo.value + assert e.get_dbus_name() == "org.freedesktop.portal.Error.InvalidArgument" + assert "Invalid token" in e.get_dbus_message() - assert session.closed + @pytest.mark.parametrize( + "template_params", ({"remotedesktop": {"force-close": 500}},) + ) + def test_create_session_signal_closed(self, portals, dbus_con): + remotedesktop_intf = xdp.get_portal_iface(dbus_con, "RemoteDesktop") + mock_intf = xdp.get_mock_iface(dbus_con) - @pytest.mark.parametrize("params", ({"force-close": 500},)) - def test_remote_desktop_create_session_signal_closed(self, portal_mock): - request = portal_mock.create_request() + request = xdp.Request(dbus_con, remotedesktop_intf) options = { "session_handle_token": "session_token0", } @@ -59,26 +76,24 @@ def test_remote_desktop_create_session_signal_closed(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) + session = xdp.Session.from_response(dbus_con, response) # Check the impl portal was called with the right args - method_calls = portal_mock.mock_interface.GetMethodCalls("CreateSession") + method_calls = mock_intf.GetMethodCalls("CreateSession") assert len(method_calls) > 0 _, args = method_calls[-1] assert args[1] == session.handle - assert args[2] == "" # appid + # assert args[2] == "" # appid, not necessary empty # Now expect the backend to close it + xdp.wait_for(lambda: session.closed) - mainloop = GLib.MainLoop() - GLib.timeout_add(2000, mainloop.quit) - mainloop.run() - - assert session.closed + def test_connect_to_eis(self, portals, dbus_con): + remotedesktop_intf = xdp.get_portal_iface(dbus_con, "RemoteDesktop") - def test_remote_desktop_connect_to_eis(self, portal_mock): - request = portal_mock.create_request() + request = xdp.Request(dbus_con, remotedesktop_intf) options = { "session_handle_token": "session_token0", } @@ -87,10 +102,11 @@ def test_remote_desktop_connect_to_eis(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) - request = portal_mock.create_request() + session = xdp.Session.from_response(dbus_con, response) + request = xdp.Request(dbus_con, remotedesktop_intf) options = { "types": dbus.UInt32(0x3), } @@ -99,9 +115,10 @@ def test_remote_desktop_connect_to_eis(self, portal_mock): session_handle=session.handle, options=options, ) + assert response assert response.response == 0 - request = portal_mock.create_request() + request = xdp.Request(dbus_con, remotedesktop_intf) options = {} response = request.call( "Start", @@ -109,16 +126,23 @@ def test_remote_desktop_connect_to_eis(self, portal_mock): parent_window="", options=options, ) + assert response assert response.response == 0 - rd_intf = portal_mock.get_dbus_interface() - fd = rd_intf.ConnectToEIS(session.handle, dbus.Dictionary({}, signature="sv")) + fd = remotedesktop_intf.ConnectToEIS( + session.handle, + dbus.Dictionary({}, signature="sv"), + ) eis_socket = socket.fromfd(fd.take(), socket.AF_UNIX, socket.SOCK_STREAM) assert eis_socket.recv(10) == b"HELLO" - @pytest.mark.parametrize("params", ({"fail-connect-to-eis": True},)) - def test_remote_desktop_connect_to_eis_fail(self, portal_mock): - request = portal_mock.create_request() + @pytest.mark.parametrize( + "template_params", ({"remotedesktop": {"fail-connect-to-eis": True}},) + ) + def test_connect_to_eis_fail(self, portals, dbus_con): + remotedesktop_intf = xdp.get_portal_iface(dbus_con, "RemoteDesktop") + + request = xdp.Request(dbus_con, remotedesktop_intf) options = { "session_handle_token": "session_token0", } @@ -127,10 +151,11 @@ def test_remote_desktop_connect_to_eis_fail(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) - request = portal_mock.create_request() + session = xdp.Session.from_response(dbus_con, response) + request = xdp.Request(dbus_con, remotedesktop_intf) options = { "types": dbus.UInt32(0x3), } @@ -139,9 +164,10 @@ def test_remote_desktop_connect_to_eis_fail(self, portal_mock): session_handle=session.handle, options=options, ) + assert response assert response.response == 0 - request = portal_mock.create_request() + request = xdp.Request(dbus_con, remotedesktop_intf) options = {} response = request.call( "Start", @@ -149,17 +175,19 @@ def test_remote_desktop_connect_to_eis_fail(self, portal_mock): parent_window="", options=options, ) + assert response assert response.response == 0 with pytest.raises(dbus.exceptions.DBusException) as excinfo: - rd_intf = portal_mock.get_dbus_interface() - _ = rd_intf.ConnectToEIS( + _ = remotedesktop_intf.ConnectToEIS( session.handle, dbus.Dictionary({}, signature="sv") ) assert "Purposely failing ConnectToEIS" in excinfo.value.get_dbus_message() - def test_remote_desktop_connect_to_eis_fail_notifies(self, portal_mock): - request = portal_mock.create_request() + def test_connect_to_eis_fail_notifies(self, portals, dbus_con): + remotedesktop_intf = xdp.get_portal_iface(dbus_con, "RemoteDesktop") + + request = xdp.Request(dbus_con, remotedesktop_intf) options = { "session_handle_token": "session_token0", } @@ -168,10 +196,11 @@ def test_remote_desktop_connect_to_eis_fail_notifies(self, portal_mock): options=options, ) + assert response assert response.response == 0 - session = Session.from_response(portal_mock.dbus_con, response) - request = portal_mock.create_request() + session = xdp.Session.from_response(dbus_con, response) + request = xdp.Request(dbus_con, remotedesktop_intf) options = { "types": dbus.UInt32(0x3), } @@ -180,9 +209,10 @@ def test_remote_desktop_connect_to_eis_fail_notifies(self, portal_mock): session_handle=session.handle, options=options, ) + assert response assert response.response == 0 - request = portal_mock.create_request() + request = xdp.Request(dbus_con, remotedesktop_intf) options = {} response = request.call( "Start", @@ -190,9 +220,10 @@ def test_remote_desktop_connect_to_eis_fail_notifies(self, portal_mock): parent_window="", options=options, ) + assert response assert response.response == 0 - for notifyfunc in [ + notifyfuncs: List[Dict[str, Any]] = [ {"name": "NotifyPointerMotion", "args": (1, 2)}, {"name": "NotifyPointerMotionAbsolute", "args": (0, 1, 2)}, {"name": "NotifyPointerButton", "args": (1, 1)}, @@ -203,15 +234,15 @@ def test_remote_desktop_connect_to_eis_fail_notifies(self, portal_mock): {"name": "NotifyTouchDown", "args": (0, 0, 1, 1)}, {"name": "NotifyTouchMotion", "args": (0, 0, 1, 1)}, {"name": "NotifyTouchUp", "args": (0,)}, - ]: + ] + for notifyfunc in notifyfuncs: with pytest.raises(dbus.exceptions.DBusException) as excinfo: - rd_intf = portal_mock.get_dbus_interface() - func = getattr(rd_intf, notifyfunc["name"]) + func = getattr(remotedesktop_intf, notifyfunc["name"]) assert func is not None func( session.handle, dbus.Dictionary({}, signature="sv"), - *notifyfunc["args"] + *notifyfunc["args"], ) # Not the best error message but... assert ( diff --git a/tests/test_screenshot.py b/tests/test_screenshot.py new file mode 100644 index 0000000..d0b2999 --- /dev/null +++ b/tests/test_screenshot.py @@ -0,0 +1,187 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import dbus +import pytest +from typing import Any + + +SCREENSHOT_DATA = dbus.Dictionary( + { + "uri": "file:///screenshot.png", + "color": (0.0, 1.0, 0.331), + }, + signature="sv", +) + + +@pytest.fixture +def required_templates(): + return { + "access": {}, + "screenshot": { + "results": SCREENSHOT_DATA, + }, + } + + +class TestScreenshot: + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Screenshot", 2) + + @pytest.mark.parametrize("modal", [True, False]) + @pytest.mark.parametrize("interactive", [True, False]) + def test_screenshot_basic(self, portals, dbus_con, app_id, modal, interactive): + screenshot_intf = xdp.get_portal_iface(dbus_con, "Screenshot") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, screenshot_intf) + options = { + "modal": modal, + "interactive": interactive, + } + response = request.call( + "Screenshot", + parent_window="", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["uri"] == SCREENSHOT_DATA["uri"] + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Screenshot") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3]["modal"] == modal + assert args[3]["interactive"] == interactive + + # check that args were forwarded to access portal correctly + if not interactive: + method_calls = mock_intf.GetMethodCalls("AccessDialog") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[6]["modal"] == modal + + @pytest.mark.parametrize( + "template_params", ({"screenshot": {"expect-close": True}},) + ) + def test_screenshot_close(self, portals, dbus_con): + screenshot_intf = xdp.get_portal_iface(dbus_con, "Screenshot") + + request = xdp.Request(dbus_con, screenshot_intf) + request.schedule_close(1000) + options = { + "interactive": True, + } + request.call( + "Screenshot", + parent_window="", + options=options, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + @pytest.mark.parametrize("template_params", ({"screenshot": {"response": 1}},)) + def test_screenshot_cancel(self, portals, dbus_con, app_id): + screenshot_intf = xdp.get_portal_iface(dbus_con, "Screenshot") + mock_intf = xdp.get_mock_iface(dbus_con) + + modal = True + interactive = True + + request = xdp.Request(dbus_con, screenshot_intf) + options = { + "modal": modal, + "interactive": interactive, + } + response = request.call( + "Screenshot", + parent_window="", + options=options, + ) + + assert response + assert response.response == 1 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("Screenshot") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3]["modal"] == modal + assert args[3]["interactive"] == interactive + + def test_pick_color_basic(self, portals, dbus_con, app_id): + screenshot_intf = xdp.get_portal_iface(dbus_con, "Screenshot") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, screenshot_intf) + options: Any = {} + response = request.call( + "PickColor", + parent_window="", + options=options, + ) + + assert response + assert response.response == 0 + assert response.results["color"] == SCREENSHOT_DATA["color"] + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("PickColor") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + + @pytest.mark.parametrize( + "template_params", ({"screenshot": {"expect-close": True}},) + ) + def test_pick_color_close(self, portals, dbus_con, app_id): + screenshot_intf = xdp.get_portal_iface(dbus_con, "Screenshot") + + request = xdp.Request(dbus_con, screenshot_intf) + request.schedule_close(1000) + options: Any = {} + request.call( + "PickColor", + parent_window="", + options=options, + ) + + # Only true if the impl.Request was closed too + assert request.closed + + @pytest.mark.parametrize("template_params", ({"screenshot": {"response": 1}},)) + def test_pick_color_cancel(self, portals, dbus_con, app_id): + screenshot_intf = xdp.get_portal_iface(dbus_con, "Screenshot") + mock_intf = xdp.get_mock_iface(dbus_con) + + request = xdp.Request(dbus_con, screenshot_intf) + options: Any = {} + response = request.call( + "PickColor", + parent_window="", + options=options, + ) + + assert response + assert response.response == 1 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("PickColor") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window diff --git a/tests/test_settings.py b/tests/test_settings.py new file mode 100644 index 0000000..2f663a5 --- /dev/null +++ b/tests/test_settings.py @@ -0,0 +1,338 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import dbus +import pytest + + +SETTINGS_DATA_TEST1 = { + "org.freedesktop.appearance": dbus.Dictionary( + { + "color-scheme": dbus.UInt32(1), + "accent-color": dbus.Struct((0.0, 0.1, 0.33), signature="ddd"), + }, + signature="sv", + ), +} + +SETTINGS_DATA_TEST2 = { + "org.freedesktop.appearance": dbus.Dictionary( + { + "color-scheme": dbus.UInt32(2), + "contrast": dbus.UInt32(0), + }, + signature="sv", + ), + "org.example.custom": dbus.Dictionary( + { + "foo": "bar", + }, + signature="sv", + ), +} + +SETTINGS_DATA_BAD = { + "org.freedesktop.appearance": dbus.Dictionary( + { + "color-scheme": dbus.UInt32(99), + "accent-color": dbus.Struct((11.11, 22.22, 33.33), signature="ddd"), + }, + signature="sv", + ), + "org.example.custom": dbus.Dictionary( + { + "foo": "baz", + }, + signature="sv", + ), + "org.example.custom.bad": dbus.Dictionary( + { + "bad": "bad", + }, + signature="sv", + ), +} + +# This is the expected data, merged SETTINGS_DATA_TEST1 and SETTINGS_DATA_TEST2 +SETTINGS_DATA = { + "org.freedesktop.appearance": dbus.Dictionary( + { + "color-scheme": dbus.UInt32(1), + "accent-color": dbus.Struct((0.0, 0.1, 0.33), signature="ddd"), + "contrast": dbus.UInt32(0), + }, + signature="sv", + ), + "org.example.custom": dbus.Dictionary( + { + "foo": "bar", + }, + signature="sv", + ), +} + + +@pytest.fixture +def required_templates(): + return { + "settings:org.freedesktop.impl.portal.Test1": { + "settings": SETTINGS_DATA_TEST1, + }, + "settings:org.freedesktop.impl.portal.Test2": { + "settings": SETTINGS_DATA_TEST2, + }, + "settings:org.freedesktop.impl.portal.TestBad": { + "settings": SETTINGS_DATA_BAD, + }, + } + + +PORTAL_CONFIG_FILES = { + "test1.portal": b""" +[portal] +DBusName=org.freedesktop.impl.portal.Test1 +Interfaces=org.freedesktop.impl.portal.Settings; +""", + "test2.portal": b""" +[portal] +DBusName=org.freedesktop.impl.portal.Test2 +Interfaces=org.freedesktop.impl.portal.Settings; +""", + "test_bad.portal": b""" +[portal] +DBusName=org.freedesktop.impl.portal.TestBad +Interfaces=org.freedesktop.impl.portal.Settings; +""", + "test_noimpl.portal": b""" +[portal] +DBusName=org.freedesktop.impl.portal.TestBad +Interfaces=org.freedesktop.impl.portal.NonExistant; +""", +} + + +def portal_config_good(): + # test1 merged with test2 should result in the correct output + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test1;test2; +""" + yield files + + # a portal without the settings impl does not affect the result + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test1;test_noimpl;test2; +""" + yield files + + # the default should be ignored when the interface is configured + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test_bad; +org.freedesktop.impl.portal.Settings=test1;test2 +""" + yield files + + # use * which should expand to test1;test2;test_noimpl + files = PORTAL_CONFIG_FILES.copy() + del files["test_bad.portal"] + files["test-portals.conf"] = b""" +[preferred] +default=test_noimpl; +org.freedesktop.impl.portal.Settings=*; +""" + yield files + + +def portal_config_bad(): + # test1 alone should result in bad output + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test1; +""" + yield files + + # test2 merged with test1 is the wrong order + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test2;test1; +""" + yield files + + # test_noimpl does not affect anything + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test_noimpl;test2;test1; +""" + yield files + + # default should get ignored, test2 alone should result in bad output + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test1;test2 +org.freedesktop.impl.portal.Settings=test2;test_noimpl +""" + yield files + + # test_bad anywhere in the active config should result in bad output + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test1;test2 +org.freedesktop.impl.portal.Settings=test_bad;test1;test2 +""" + yield files + + # use * which expands to test1;test2;test_bad;test_no_impl + # contains test_bad which should result in bad output + files = PORTAL_CONFIG_FILES.copy() + files["test-portals.conf"] = b""" +[preferred] +default=test_noimpl; +org.freedesktop.impl.portal.Settings=*; +""" + yield files + + +def portal_config_twice(): + # check that test1 gets picked up only once + files = PORTAL_CONFIG_FILES.copy() + del files["test_bad.portal"] + files["test-portals.conf"] = b""" +[preferred] +default=test_noimpl; +org.freedesktop.impl.portal.Settings=test1;*; +""" + yield files + + +@pytest.fixture +def xdg_desktop_portal_dir_default_files(): + return next(portal_config_good()) + + +class TestSettings: + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Settings", 2) + + @pytest.mark.parametrize( + "xdg_desktop_portal_dir_default_files", + portal_config_good(), + ) + def test_read_all(self, portals, dbus_con): + settings_intf = xdp.get_portal_iface(dbus_con, "Settings") + + value = settings_intf.ReadAll([]) + assert value == SETTINGS_DATA + + value = settings_intf.ReadAll([""]) + assert value == SETTINGS_DATA + + value = settings_intf.ReadAll(["does-not-exist"]) + assert value == {} + + value = settings_intf.ReadAll(["org."]) + assert value == {} + + value = settings_intf.ReadAll(["org.*"]) + assert value == SETTINGS_DATA + + value = settings_intf.ReadAll( + ["org.freedesktop.appearance", "org.example.custom"] + ) + assert value == SETTINGS_DATA + + value = settings_intf.ReadAll(["org.freedesktop.appearance"]) + assert len(value) == 1 + assert "org.freedesktop.appearance" in value + assert ( + value["org.freedesktop.appearance"] + == SETTINGS_DATA["org.freedesktop.appearance"] + ) + + @pytest.mark.parametrize( + "xdg_desktop_portal_dir_default_files", + portal_config_bad(), + ) + def test_read_all_bad_config(self, portals, dbus_con): + settings_intf = xdp.get_portal_iface(dbus_con, "Settings") + + value = settings_intf.ReadAll([]) + assert value != SETTINGS_DATA + + @pytest.mark.parametrize( + "xdg_desktop_portal_dir_default_files", + portal_config_twice(), + ) + def test_config_twice(self, portals, dbus_con): + settings_intf = xdp.get_portal_iface(dbus_con, "Settings") + mock_intf = xdp.get_mock_iface(dbus_con, "org.freedesktop.impl.portal.Test1") + + value = settings_intf.ReadAll([]) + assert value == SETTINGS_DATA + + # The config is `test1;*`, make sure we only get a single call to Test1 + method_calls = mock_intf.GetMethodCalls("ReadAll") + assert len(method_calls) == 1 + + def test_read(self, portals, dbus_con): + settings_intf = xdp.get_portal_iface(dbus_con, "Settings") + + color_scheme = SETTINGS_DATA["org.freedesktop.appearance"]["color-scheme"] + + value = settings_intf.ReadOne("org.freedesktop.appearance", "color-scheme") + assert isinstance(value, dbus.UInt32) + assert value.variant_level == 1 + assert value == color_scheme + + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + settings_intf.ReadOne("org.does.not.exist", "color-scheme") + assert excinfo.value.get_dbus_name() == "org.freedesktop.portal.Error.NotFound" + + with pytest.raises(dbus.exceptions.DBusException) as excinfo: + settings_intf.ReadOne("org.freedesktop.appearance", "xcolor-scheme") + assert excinfo.value.get_dbus_name() == "org.freedesktop.portal.Error.NotFound" + + # deprecated but should still check that it works + # the crucial detail here is that the variant_level is 2 + value = settings_intf.Read("org.freedesktop.appearance", "color-scheme") + assert isinstance(value, dbus.UInt32) + assert value.variant_level == 2 + assert value == color_scheme + + def test_changed(self, portals, dbus_con): + settings_intf = xdp.get_portal_iface(dbus_con, "Settings") + mock_intf = xdp.get_mock_iface(dbus_con, "org.freedesktop.impl.portal.Test1") + changed_count = 0 + + ns = "org.freedesktop.appearance" + key = "color-scheme" + current_value = SETTINGS_DATA[ns][key] + new_value = 2 + assert current_value != new_value + + value = settings_intf.ReadOne(ns, key) + assert value == current_value + + def cb_settings_changed(changed_ns, changed_key, changed_value): + nonlocal changed_count + changed_count += 1 + assert changed_ns == ns + assert changed_key == key + assert changed_value == new_value + + settings_intf.connect_to_signal("SettingChanged", cb_settings_changed) + mock_intf.SetSetting(ns, key, new_value) + + xdp.wait_for(lambda: changed_count == 1) diff --git a/tests/test_trash.py b/tests/test_trash.py index d745cd2..2637256 100644 --- a/tests/test_trash.py +++ b/tests/test_trash.py @@ -2,37 +2,26 @@ # # This file is formatted with Python Black - -from pathlib import Path +import tests as xdp import os -import pytest import tempfile - - -@pytest.fixture -def portal_name(): - return "Trash" - - -@pytest.fixture -def portal_has_impl(): - return False +from pathlib import Path class TestTrash: - def test_version(self, portal_mock): - portal_mock.check_version(1) + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Trash", 1) - def test_trash_file_fails(self, portal_mock): - trash_intf = portal_mock.get_dbus_interface() + def test_trash_file_fails(self, portals, dbus_con): + trash_intf = xdp.get_portal_iface(dbus_con, "Trash") with open("/proc/cmdline") as fd: result = trash_intf.TrashFile(fd.fileno()) assert result == 0 - def test_trash_file(self, portal_mock): - trash_intf = portal_mock.get_dbus_interface() + def test_trash_file(self, portals, dbus_con): + trash_intf = xdp.get_portal_iface(dbus_con, "Trash") fd, name = tempfile.mkstemp(prefix="trash_portal_mock_", dir=Path.home()) result = trash_intf.TrashFile(fd) diff --git a/tests/test_usb.py b/tests/test_usb.py new file mode 100644 index 0000000..affc6c3 --- /dev/null +++ b/tests/test_usb.py @@ -0,0 +1,384 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import pytest +import os +import gi +import subprocess +import re + +gi.require_version("UMockdev", "1.0") +from gi.repository import GLib, UMockdev # noqa E402 + + +@pytest.fixture +def required_templates(): + return {"usb": {}} + + +@pytest.fixture +def umockdev(): + return UMockdev.Testbed.new() + + +def umockdev_has_working_remove(): + # umockdev only generates remove events since version 0.18.4 + # https://github.com/martinpitt/umockdev/releases/tag/0.18.4 + required = (0, 18, 4) + + result = subprocess.run(["umockdev-run", "--version"], stdout=subprocess.PIPE) + if result.returncode != 0: + return False + match = re.match(r"^(\d+)\.(\d+)\.(\d+)", result.stdout.decode("UTF-8").strip()) + if not match: + return False + version = tuple(map(int, match.groups())) + return version >= required + + +class TestUsb: + _num_devices = 0 + + def generate_device( + self, testbed, vendor, vendor_name, product, product_name, serial + ): + n = self._num_devices + self._num_devices += 1 + + testbed.add_from_string(f"""P: /devices/usb{n} +N: bus/usb/001/{n:03d} +E: BUSNUM=001 +E: DEVNUM={n:03d} +E: DEVNAME=/dev/bus/usb/001/{n:03d} +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_BUS=usb +E: ID_MODEL={product_name} +E: ID_MODEL_ID={product} +E: ID_REVISION=0002 +E: ID_SERIAL={vendor_name}_{product_name}_{serial} +E: ID_SERIAL_SHORT={serial} +E: ID_VENDOR={vendor_name} +E: ID_VENDOR_ID={vendor} +E: SUBSYSTEM=usb +A: idProduct={product} +A: idVendor={vendor} +""") + + return f"/sys/devices/usb{n}" + + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Usb", 1) + + def test_create_close_session(self, portals, dbus_con, app_id): + usb_intf = xdp.get_portal_iface(dbus_con, "Usb") + + session = xdp.Session( + dbus_con, + usb_intf.CreateSession({"session_handle_token": "session_token0"}), + ) + session.close() + + def test_empty_initial_devices(self, portals, dbus_con, app_id): + usb_intf = xdp.get_portal_iface(dbus_con, "Usb") + + xdp.Session( + dbus_con, + usb_intf.CreateSession({"session_handle_token": "session_token0"}), + ) + + device_events_signal_received = False + + def cb_device_events(session_handle, events): + nonlocal device_events_signal_received + device_events_signal_received = True + + usb_intf.connect_to_signal("DeviceEvents", cb_device_events) + xdp.wait(300) + assert not device_events_signal_received + + @pytest.mark.parametrize("usb_queries", ["vnd:04a9", None]) + def test_initial_devices(self, portals, dbus_con, app_id, usb_queries, umockdev): + usb_intf = xdp.get_portal_iface(dbus_con, "Usb") + + self.generate_device( + umockdev, + "04a9", + "Canon_Inc.", + "31c0", + "Canon_Digital_Camera", + "C767F1C714174C309255F70E4A7B2EE2", + ) + + # TODO: to make this more robust, we should find a way to wait for + # the portal to pick up the device + xdp.wait(300) + + session = xdp.Session( + dbus_con, + usb_intf.CreateSession({"session_handle_token": "session_token0"}), + ) + + device_events_signal_received = False + devices_received = 0 + + def cb_device_events(session_handle, events): + nonlocal device_events_signal_received + nonlocal devices_received + assert session.handle == session_handle + + for action, id, device in events: + assert action == "add" + devices_received += 1 + + device_events_signal_received = True + + usb_intf.connect_to_signal("DeviceEvents", cb_device_events) + + if usb_queries is None: + xdp.wait(300) + assert not device_events_signal_received + assert devices_received == 0 + else: + xdp.wait_for(lambda: device_events_signal_received) + assert devices_received == 1 + + @pytest.mark.parametrize("usb_queries", ["vnd:04a9", None]) + def test_device_add(self, portals, dbus_con, app_id, usb_queries, umockdev): + usb_intf = xdp.get_portal_iface(dbus_con, "Usb") + + session = xdp.Session( + dbus_con, + usb_intf.CreateSession({"session_handle_token": "session_token0"}), + ) + + device_events_signal_received = False + devices_received = 0 + device = None + + def cb_device_events(session_handle, events): + nonlocal device_events_signal_received + nonlocal devices_received + nonlocal device + assert session.handle == session_handle + + for action, _, dev in events: + assert action == "add" + device = dev + devices_received += 1 + + device_events_signal_received = True + + usb_intf.connect_to_signal("DeviceEvents", cb_device_events) + xdp.wait(300) + assert not device_events_signal_received + + self.generate_device( + umockdev, + "04a9", + "Canon_Inc.", + "31c0", + "Canon_Digital_Camera", + "C767F1C714174C309255F70E4A7B2EE2", + ) + + if usb_queries is None: + xdp.wait(300) + assert not device_events_signal_received + assert devices_received == 0 + else: + xdp.wait_for(lambda: device_events_signal_received) + assert devices_received == 1 + + assert device + assert device["readable"] + assert device["writable"] + assert device["device-file"] == "/dev/bus/usb/001/000" + assert device["properties"]["ID_VENDOR_ID"] == "04a9" + assert device["properties"]["ID_MODEL_ID"] == "31c0" + assert ( + device["properties"]["ID_SERIAL"] + == "Canon_Inc._Canon_Digital_Camera_C767F1C714174C309255F70E4A7B2EE2" + ) + + @pytest.mark.parametrize("usb_queries", ["vnd:04a9", None]) + @pytest.mark.skipif( + not umockdev_has_working_remove(), reason="UMockdev version 0.18.4 required" + ) + def test_device_remove(self, portals, dbus_con, app_id, usb_queries, umockdev): + usb_intf = xdp.get_portal_iface(dbus_con, "Usb") + + dev_path = self.generate_device( + umockdev, + "04a9", + "Canon_Inc.", + "31c0", + "Canon_Digital_Camera", + "C767F1C714174C309255F70E4A7B2EE2", + ) + + session = xdp.Session( + dbus_con, + usb_intf.CreateSession({"session_handle_token": "session_token0"}), + ) + + device_events_signal_count = 0 + devices_received = 0 + devices_removed = 0 + + def cb_device_events(session_handle, events): + nonlocal device_events_signal_count + nonlocal devices_received + nonlocal devices_removed + + assert session.handle == session_handle + + for action, id, device in events: + if action == "add": + devices_received += 1 + elif action == "remove": + devices_removed += 1 + else: + assert False + + device_events_signal_count += 1 + + usb_intf.connect_to_signal("DeviceEvents", cb_device_events) + + if usb_queries is None: + xdp.wait(300) + assert device_events_signal_count == 0 + assert devices_received == 0 + assert devices_removed == 0 + else: + xdp.wait_for(lambda: device_events_signal_count == 1) + assert devices_received == 1 + assert devices_removed == 0 + + umockdev.remove_device(dev_path) + + if usb_queries is None: + xdp.wait(300) + assert device_events_signal_count == 0 + assert devices_received == 0 + assert devices_removed == 0 + else: + xdp.wait_for(lambda: device_events_signal_count == 2) + assert devices_received == 1 + assert devices_removed == 1 + + @pytest.mark.parametrize("usb_queries", ["vnd:04a9;vnd:04aa"]) + @pytest.mark.parametrize( + "template_params", [{"usb": {"filters": {"vendor": "04a9"}}}] + ) + def test_acquire(self, portals, dbus_con, app_id, umockdev): + usb_intf = xdp.get_portal_iface(dbus_con, "Usb") + + self.generate_device( + umockdev, + "04a9", + "Canon_Inc.", + "31c0", + "Canon_Digital_Camera", + "C767F1C714174C309255F70E4A7B2EE2", + ) + + self.generate_device( + umockdev, + "04aa", + "Someone Else.", + "31c0", + "SomeProduct", + "00001", + ) + + possible_vendors = ["04a9", "04aa"] + + devices = usb_intf.EnumerateDevices({}) + assert len(devices) == 2 + (id1, dev_info1) = devices[0] + assert id1 + assert dev_info1 + vendor_id = dev_info1["properties"]["ID_VENDOR_ID"] + assert vendor_id in possible_vendors + possible_vendors.remove(vendor_id) + (id2, dev_info2) = devices[1] + assert id2 + assert dev_info2 + vendor_id = dev_info2["properties"]["ID_VENDOR_ID"] + assert vendor_id in possible_vendors + possible_vendors.remove(vendor_id) + + request = xdp.Request(dbus_con, usb_intf) + response = request.call( + "AcquireDevices", + parent_window="", + devices=[ + (id1, {"writable": True}), + (id2, {"writable": True}), + ], + options={}, + ) + assert response + assert response.response == 0 + + (results, finished) = usb_intf.FinishAcquireDevices(request.handle, {}) + assert finished + assert len(results) == 1 + (res_id, device) = results[0] + assert res_id == id1 or res_id == id2 + assert device["success"] + fd = device["fd"].take() + assert fd > 0 + with os.fdopen(fd, "r") as f: + assert f + assert "error" not in device + + usb_intf.ReleaseDevices([res_id], {}) + + @pytest.mark.parametrize("usb_queries", ["vnd:0001"]) + @pytest.mark.parametrize( + "expected,template_params", + [ + (1, {"usb": {"filters": {"model": "0000"}}}), + (1, {"usb": {"filters": {"model": "0001"}}}), + (0, {"usb": {"filters": {"model": "0002"}}}), + (2, {"usb": {"filters": {"vendor": "0001"}}}), + (0, {"usb": {"filters": {"vendor": "0002"}}}), + (1, {"usb": {"filters": {"vendor": "0001", "model": "0000"}}}), + (0, {"usb": {"filters": {"vendor": "0002", "model": "0000"}}}), + ], + ) + def test_queries(self, portals, dbus_con, expected, app_id, usb_queries, umockdev): + usb_intf = xdp.get_portal_iface(dbus_con, "Usb") + + for i in range(2): + self.generate_device( + umockdev, + "0001", + "example_org", + f"000{i}", + f"model{i}", + "0001", + ) + + devices = usb_intf.EnumerateDevices({}) + assert len(devices) == 2 + acquire_devices = [(id, {"writable": True}) for (id, _) in devices] + + request = xdp.Request(dbus_con, usb_intf) + response = request.call( + "AcquireDevices", + parent_window="", + devices=acquire_devices, + options={}, + ) + assert response + assert response.response == 0 + + (results, finished) = usb_intf.FinishAcquireDevices(request.handle, {}) + assert finished + assert len(results) == expected diff --git a/tests/test_wallpaper.py b/tests/test_wallpaper.py new file mode 100644 index 0000000..803405b --- /dev/null +++ b/tests/test_wallpaper.py @@ -0,0 +1,153 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is formatted with Python Black + +import tests as xdp + +import pytest +import os +import tempfile +from pathlib import Path + + +@pytest.fixture +def required_templates(): + return {"wallpaper": {}} + + +class TestWallpaper: + def set_permission(self, dbus_con, app_id, permission): + perm_store_intf = xdp.get_permission_store_iface(dbus_con) + perm_store_intf.SetPermission( + "wallpaper", + True, + "wallpaper", + app_id, + [permission], + ) + + def test_version(self, portals, dbus_con): + xdp.check_version(dbus_con, "Wallpaper", 1) + + def test_wallpaper_uri(self, portals, dbus_con, app_id): + wallpaper_intf = xdp.get_portal_iface(dbus_con, "Wallpaper") + mock_intf = xdp.get_mock_iface(dbus_con) + + uri = "file:///test" + show_preview = True + set_on = "both" + + request = xdp.Request(dbus_con, wallpaper_intf) + options = { + "show-preview": show_preview, + "set-on": set_on, + } + response = request.call( + "SetWallpaperURI", + parent_window="", + uri=uri, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("SetWallpaperURI") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[3] == uri + assert args[4]["show-preview"] == show_preview + assert args[4]["set-on"] == set_on + + def test_wallpaper_file(self, portals, dbus_con, app_id): + wallpaper_intf = xdp.get_portal_iface(dbus_con, "Wallpaper") + mock_intf = xdp.get_mock_iface(dbus_con) + + fd, _ = tempfile.mkstemp(prefix="wallpaper_mock", dir=Path.home()) + os.write(fd, b"wallpaper_mock_file") + + show_preview = True + + request = xdp.Request(dbus_con, wallpaper_intf) + options = { + "show-preview": show_preview, + } + response = request.call( + "SetWallpaperFile", + parent_window="", + fd=fd, + options=options, + ) + + assert response + assert response.response == 0 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("SetWallpaperURI") + assert len(method_calls) > 0 + _, args = method_calls[-1] + assert args[1] == app_id + assert args[2] == "" # parent window + assert args[4]["show-preview"] == show_preview + + path = args[3] + assert path.startswith("file:///") + + with open(path[7:]) as file: + wallpaper_file_contents = file.read() + assert wallpaper_file_contents == "wallpaper_mock_file" + + @pytest.mark.parametrize("template_params", ({"wallpaper": {"response": 1}},)) + def test_wallpaper_cancel(self, portals, dbus_con, app_id): + wallpaper_intf = xdp.get_portal_iface(dbus_con, "Wallpaper") + + uri = "file:///test" + show_preview = True + set_on = "both" + + request = xdp.Request(dbus_con, wallpaper_intf) + options = { + "show-preview": show_preview, + "set-on": set_on, + } + response = request.call( + "SetWallpaperURI", + parent_window="", + uri=uri, + options=options, + ) + + assert response + assert response.response == 1 + + def test_wallpaper_permission(self, portals, dbus_con, app_id): + wallpaper_intf = xdp.get_portal_iface(dbus_con, "Wallpaper") + mock_intf = xdp.get_mock_iface(dbus_con) + + self.set_permission(dbus_con, app_id, "no") + + uri = "file:///test" + show_preview = True + set_on = "both" + + request = xdp.Request(dbus_con, wallpaper_intf) + options = { + "show-preview": show_preview, + "set-on": set_on, + } + response = request.call( + "SetWallpaperURI", + parent_window="", + uri=uri, + options=options, + ) + + assert response + assert response.response == 2 + + # Check the impl portal was called with the right args + method_calls = mock_intf.GetMethodCalls("SetWallpaperURI") + assert len(method_calls) == 0 diff --git a/tests/trash.c b/tests/trash.c deleted file mode 100644 index 5dc945f..0000000 --- a/tests/trash.c +++ /dev/null @@ -1,47 +0,0 @@ -#include - -#include "trash.h" - -#include - -static gboolean got_info; - -static void -trash_cb (GObject *object, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (object); - gboolean expected = *(gboolean*)data; - gboolean ret; - g_autoptr(GError) error = NULL; - - ret = xdp_portal_trash_file_finish (portal, result, &error); - g_assert_cmpint (ret, ==, expected); - if (ret) - g_assert_no_error (error); - else - g_assert (error != NULL); - - got_info = TRUE; - g_main_context_wakeup (NULL); -} - -/* Reliably testing successful trashing in a CI environment - * is hard, so just test something that is sure to fail. - */ -void -test_trash_file (void) -{ - g_autoptr(XdpPortal) portal = NULL; - gboolean expected; - - portal = xdp_portal_new (); - - expected = FALSE; - got_info = FALSE; - xdp_portal_trash_file (portal, "/proc/cmdline", NULL, trash_cb, &expected); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} diff --git a/tests/trash.h b/tests/trash.h deleted file mode 100644 index 630768e..0000000 --- a/tests/trash.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void test_trash_file (void); diff --git a/tests/utils.c b/tests/utils.c deleted file mode 100644 index ebe15ce..0000000 --- a/tests/utils.c +++ /dev/null @@ -1,61 +0,0 @@ -#include - -#include "utils.h" - -#include - -/* - * Set a property. Unlike gdbus-codegen-generated wrapper functions, this - * waits for the property change to take effect. - * - * If @value is floating, ownership is taken. - */ -gboolean -tests_set_property_sync (GDBusProxy *proxy, - const char *iface, - const char *property, - GVariant *value, - GError **error) -{ - g_autoptr (GVariant) res = NULL; - - res = g_dbus_proxy_call_sync (proxy, - "org.freedesktop.DBus.Properties.Set", - g_variant_new ("(ssv)", iface, property, value), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - error); - return (res != NULL); -} - -/* We need this to ensure that dbus-daemon launched by GTestDBus is not - * causing our tests to hang (see GNOME/glib#2537), so we are redirecting - * all its output to stderr, while reading its pid and address to manage it. - * As bonus point, now the services output will be visible in test logs. - * This can be removed once GNOME/glib!2354 will be available everywhere. - */ -void -setup_dbus_daemon_wrapper (const char *outdir) -{ - g_autofree gchar *file_name = NULL; - g_autofree gchar *test_path = NULL; - g_autoptr (GError) error = NULL; - const char dbus_daemon_script[] = \ - "#!/usr/bin/env bash\n" - "export PATH=\"$ORIGINAL_PATH\"\n" - "\n" - "[[ \" ${@} \" =~ \" --print-address=\"[0-9]+\" \" ]] && " - " exec dbus-daemon \"$@\"\n" - "\n" - "exec dbus-daemon \"$@\" --print-address=959 959<&1 1>&2\n"; - - test_path = g_strjoin (":", outdir, g_getenv ("PATH"), NULL); - g_setenv ("ORIGINAL_PATH", g_getenv ("PATH"), TRUE); - g_setenv ("PATH", test_path, TRUE); - - file_name = g_build_filename (outdir, "dbus-daemon", NULL); - g_file_set_contents (file_name, dbus_daemon_script, -1, &error); - g_chmod (file_name, 0700); - g_assert_no_error (error); -} diff --git a/tests/utils.h b/tests/utils.h deleted file mode 100644 index fae5d85..0000000 --- a/tests/utils.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -gboolean tests_set_property_sync (GDBusProxy *proxy, - const char *iface, - const char *property, - GVariant *value, - GError **error); - -void setup_dbus_daemon_wrapper (const char *outdir); diff --git a/tests/wallpaper.c b/tests/wallpaper.c deleted file mode 100644 index d6fdc18..0000000 --- a/tests/wallpaper.c +++ /dev/null @@ -1,294 +0,0 @@ -#include - -#include "account.h" - -#include -#include "xdp-impl-dbus.h" - -extern char outdir[]; - -static int got_info = 0; - -extern XdpDbusImplPermissionStore *permission_store; - -static void -set_wallpaper_permissions (const char *permission) -{ - const char *permissions[2] = { NULL, NULL }; - g_autoptr(GError) error = NULL; - - permissions[0] = permission; - xdp_dbus_impl_permission_store_call_set_permission_sync (permission_store, - "wallpaper", - TRUE, - "wallpaper", - "", - permissions, - NULL, - &error); - g_assert_no_error (error); -} - -static void -reset_wallpaper_permissions (void) -{ - set_wallpaper_permissions (NULL); -} - -static void -wallpaper_cb (GObject *obj, - GAsyncResult *result, - gpointer data) -{ - XdpPortal *portal = XDP_PORTAL (obj); - g_autoptr(GError) error = NULL; - GKeyFile *keyfile = data; - gboolean res; - int response; - - response = g_key_file_get_integer (keyfile, "result", "response", NULL); - - res = xdp_portal_set_wallpaper_finish (portal, result, &error); - if (response == 0) - { - g_assert_true (res); - g_assert_no_error (error); - } - else if (response == 1) - { - g_assert_false (res); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); - } - else if (response == 2) - { - g_assert_false (res); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); - } - else - g_assert_not_reached (); - - got_info++; - - g_main_context_wakeup (NULL); -} - -static const char * -target_to_string (XdpWallpaperFlags target) -{ - const char *strings[] = { "", "background", "lockscreen", "both" }; - return strings[target & 3]; -} - -void -test_wallpaper_basic (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - XdpWallpaperFlags target = XDP_WALLPAPER_FLAG_BACKGROUND | XDP_WALLPAPER_FLAG_LOCKSCREEN; - - reset_wallpaper_permissions (); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_string (keyfile, "wallpaper", "target", target_to_string (target)); - g_key_file_set_boolean (keyfile, "wallpaper", "preview", FALSE); - - path = g_build_filename (outdir, "wallpaper", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_set_wallpaper (portal, NULL, uri, target, NULL, wallpaper_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_wallpaper_delay (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - XdpWallpaperFlags target = XDP_WALLPAPER_FLAG_LOCKSCREEN; - - reset_wallpaper_permissions (); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_string (keyfile, "wallpaper", "target", target_to_string (target)); - g_key_file_set_boolean (keyfile, "wallpaper", "preview", FALSE); - - path = g_build_filename (outdir, "wallpaper", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_set_wallpaper (portal, NULL, uri, target, NULL, wallpaper_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_wallpaper_cancel1 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - XdpWallpaperFlags target = XDP_WALLPAPER_FLAG_BACKGROUND; - - reset_wallpaper_permissions (); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_string (keyfile, "wallpaper", "target", target_to_string (target)); - g_key_file_set_boolean (keyfile, "wallpaper", "preview", FALSE); - - path = g_build_filename (outdir, "wallpaper", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_set_wallpaper (portal, NULL, uri, target, NULL, wallpaper_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_wallpaper_cancel2 (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - XdpWallpaperFlags target = XDP_WALLPAPER_FLAG_BACKGROUND | XDP_WALLPAPER_FLAG_LOCKSCREEN | XDP_WALLPAPER_FLAG_PREVIEW; - - reset_wallpaper_permissions (); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 1); - g_key_file_set_string (keyfile, "wallpaper", "target", target_to_string (target)); - g_key_file_set_boolean (keyfile, "wallpaper", "preview", TRUE); - - path = g_build_filename (outdir, "wallpaper", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_set_wallpaper (portal, NULL, uri, target, NULL, wallpaper_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} - -void -test_wallpaper_permission (void) -{ - g_autoptr(XdpPortal) portal = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GError) error = NULL; - g_autofree char *path = NULL; - g_autofree char *uri = NULL; - XdpWallpaperFlags target = XDP_WALLPAPER_FLAG_BACKGROUND | XDP_WALLPAPER_FLAG_LOCKSCREEN | XDP_WALLPAPER_FLAG_PREVIEW; - - set_wallpaper_permissions ("no"); - - keyfile = g_key_file_new (); - - g_key_file_set_integer (keyfile, "backend", "delay", 0); - g_key_file_set_integer (keyfile, "backend", "response", 0); - g_key_file_set_integer (keyfile, "result", "response", 0); - - path = g_build_filename (outdir, "access", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - g_free (path); - - g_key_file_set_integer (keyfile, "backend", "delay", 200); - g_key_file_set_integer (keyfile, "backend", "response", 1); - g_key_file_set_integer (keyfile, "result", "response", 2); - g_key_file_set_string (keyfile, "wallpaper", "target", target_to_string (target)); - g_key_file_set_boolean (keyfile, "wallpaper", "preview", TRUE); - - path = g_build_filename (outdir, "wallpaper", NULL); - g_key_file_save_to_file (keyfile, path, &error); - g_assert_no_error (error); - - portal = xdp_portal_new (); - - uri = g_strconcat ("file://", path, NULL); - - got_info = 0; - xdp_portal_set_wallpaper (portal, NULL, uri, target, NULL, wallpaper_cb, keyfile); - - while (!got_info) - g_main_context_iteration (NULL, TRUE); -} diff --git a/tests/wallpaper.h b/tests/wallpaper.h deleted file mode 100644 index 0c443ac..0000000 --- a/tests/wallpaper.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -void test_wallpaper_basic (void); -void test_wallpaper_delay (void); -void test_wallpaper_cancel1 (void); -void test_wallpaper_cancel2 (void); -void test_wallpaper_permission (void);