pax_global_header00006660000000000000000000000064146076667110014530gustar00rootroot0000000000000052 comment=43258f924311c80744068e5af794db3c95df925f GSConnect-gnome-shell-extension-gsconnect-43258f9/000077500000000000000000000000001460766671100220445ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/.editorconfig000066400000000000000000000014211460766671100245170ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # Topmost editorconfig for project, don't ascend # to parent directories when scanning configs root = true # All files: UTF-8 with Unix-style newlines, # and a newline at the end of the file [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true # JavaScript, Python, CSS: 4-space indents [*.{js,py,css}] indent_style = space indent_size = 4 # XML, Meson, Glade: 2-space indents [*.{xml,build,ui}] indent_style = space indent_size = 2 # Most JSON: 2-space indents [*.json] indent_style = space indent_size = 2 # WebExtension templates: 4-space indents [data/webextension/*.in] indent_style = space indent_size = 4 GSConnect-gnome-shell-extension-gsconnect-43258f9/.eslintrc.json000066400000000000000000000200041460766671100246340ustar00rootroot00000000000000{ "env": { "es2021": true, "shared-node-browser": true }, "extends": "eslint:recommended", "rules": { "array-bracket-newline": [ "error", "consistent" ], "array-bracket-spacing": [ "error", "never" ], "array-callback-return": "error", "arrow-spacing": "error", "block-scoped-var": "error", "block-spacing": "error", "brace-style": "error", "comma-dangle": [ "error", { "arrays": "always-multiline", "objects": "always-multiline", "functions": "never" } ], "comma-spacing": [ "error", { "before": false, "after": true } ], "comma-style": [ "error", "last" ], "computed-property-spacing": "error", "curly": [ "error", "multi-or-nest", "consistent" ], "dot-location": [ "error", "property" ], "eol-last": "error", "eqeqeq": "error", "func-call-spacing": "error", "func-name-matching": "error", "func-style": [ "error", "declaration", { "allowArrowFunctions": true } ], "grouped-accessor-pairs": [ "error", "getBeforeSet" ], "indent": [ "error", 4, { "ignoredNodes": [ "CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child" ], "MemberExpression": "off", "SwitchCase": 1 } ], "key-spacing": [ "error", { "beforeColon": false, "afterColon": true } ], "keyword-spacing": [ "error", { "before": true, "after": true } ], "linebreak-style": [ "error", "unix" ], "lines-between-class-members": "error", "max-nested-callbacks": [ "error", { "max": 5 } ], "max-statements-per-line": "error", "new-parens": "error", "no-array-constructor": "error", "no-caller": "error", "no-constant-condition": [ "error", { "checkLoops": false } ], "no-empty": [ "error", { "allowEmptyCatch": true } ], "no-extra-bind": "error", "no-implicit-coercion": [ "error", { "allow": [ "!!" ] } ], "no-iterator": "error", "no-label-var": "error", "no-lonely-if": "error", "no-loop-func": "error", "no-nested-ternary": "error", "no-new-object": "error", "no-new-wrappers": "error", "no-octal-escape": "error", "no-proto": "error", "no-prototype-builtins": "off", "no-restricted-properties": [ "error", { "object": "Lang", "property": "bind", "message": "Use arrow notation or Function.prototype.bind()" }, { "object": "Lang", "property": "Class", "message": "Use ES6 classes" }, { "object": "imports", "property": "mainloop", "message": "Use GLib main loops and timeouts" } ], "no-restricted-syntax": [ "error", { "selector": "MethodDefinition[key.name=\"_init\"] > FunctionExpression[params.length=1] > BlockStatement[body.length=1] CallExpression[arguments.length=1][callee.object.type=\"Super\"][callee.property.name=\"_init\"] > Identifier:first-child", "message": "_init() that only calls super._init() is unnecessary" }, { "selector": "MethodDefinition[key.name=\"_init\"] > FunctionExpression[params.length=0] > BlockStatement[body.length=1] CallExpression[arguments.length=0][callee.object.type=\"Super\"][callee.property.name=\"_init\"]", "message": "_init() that only calls super._init() is unnecessary" } ], "no-return-assign": "error", "no-return-await": "error", "no-self-compare": "error", "no-shadow-restricted-names": "error", "no-spaced-func": "error", "no-tabs": "error", "no-template-curly-in-string": "error", "no-throw-literal": "error", "no-trailing-spaces": "error", "no-undef-init": "error", "no-unneeded-ternary": "error", "no-unused-expressions": "error", "no-unused-vars": [ "error", { "args": "none", "vars": "local" } ], "no-useless-call": "error", "no-useless-computed-key": "error", "no-useless-concat": "error", "no-useless-constructor": "error", "no-useless-rename": "error", "no-useless-return": "error", "no-whitespace-before-property": "error", "no-with": "error", "nonblock-statement-body-position": [ "error", "below" ], "object-curly-newline": [ "error", { "consistent": true } ], "object-curly-spacing": "error", "operator-assignment": "error", "operator-linebreak": "error", "prefer-const": "error", "prefer-numeric-literals": "error", "prefer-promise-reject-errors": "error", "prefer-rest-params": "error", "prefer-spread": "error", "quotes": [ "error", "single", { "avoidEscape": true } ], "require-await": "error", "rest-spread-spacing": "error", "semi": [ "error", "always" ], "semi-spacing": [ "error", { "before": false, "after": true } ], "semi-style": "error", "space-before-blocks": "error", "space-before-function-paren": [ "error", { "named": "never", "anonymous": "always", "asyncArrow": "always" } ], "space-in-parens": "error", "space-infix-ops": [ "error", { "int32Hint": false } ], "space-unary-ops": "error", "spaced-comment": "error", "switch-colon-spacing": "error", "symbol-description": "error", "template-curly-spacing": "error", "template-tag-spacing": "error", "unicode-bom": "error", "valid-jsdoc": [ "error", { "requireReturn": false } ], "wrap-iife": [ "error", "inside" ], "yield-star-spacing": "error", "yoda": "error" }, "globals": { "ARGV": "readonly", "Debugger": "readonly", "GIRepositoryGType": "readonly", "globalThis": "readonly", "imports": "readonly", "Intl": "readonly", "log": "readonly", "logError": "readonly", "print": "readonly", "printerr": "readonly", "global": false, "debug": false, "_": false, "_C": false, "_N": false, "ngettext": false }, "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" } } GSConnect-gnome-shell-extension-gsconnect-43258f9/.eslintrc.json.license000066400000000000000000000001651460766671100262630ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-43258f9/.flake8000066400000000000000000000002311460766671100232130ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later [flake8] extend-ignore = E402 GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/000077500000000000000000000000001460766671100234045ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/ISSUE_TEMPLATE/000077500000000000000000000000001460766671100255675ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/ISSUE_TEMPLATE/bug_report.yml000066400000000000000000000060501460766671100304630ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: Bug report description: Report an issue to help us improve GSConnect body: - type: markdown attributes: value: "> **ATTENTION**: GSConnect only supports the latest, stable version of GNOME. We are no longer accepting bug reports for previous versions." - type: textarea id: summary attributes: label: Describe the bug description: "A clear and concise description of what the bug is." validations: required: true - type: textarea id: repro attributes: label: Steps to reproduce description: "List the steps to perform that will trigger the bug." placeholder: | 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error - type: textarea id: expected attributes: label: Expected behavior description: "A clear and concise description of what you expected to happen." - type: markdown attributes: value: "# System details" - type: input id: gsconnect_version attributes: label: GSConnect version description: (Launch "Mobile Settings" and select "About" from the menu) validations: required: true - type: dropdown id: source attributes: label: Installed from options: - GNOME Extensions website - GitHub releases (zip file) - OS package manager - Extension Manager - other source validations: required: true - type: input id: shell_version attributes: label: GNOME Shell version - type: input id: distro attributes: label: Linux distribution/release placeholder: "Ubuntu 22.04, Fedora 37, Debian 11, Arch, etc." - type: markdown attributes: value: "# GSConnect environment" - type: input id: devices attributes: label: Paired device(s) placeholder: Pixel 7, Motorola edge 2022, Lenovo Tab P11, etc... - type: input id: app_version attributes: label: KDE Connect app version description: (Open the app's menu and tap "About") placeholder: 1.19.1 - type: input id: plugins attributes: label: Plugin(s) description: "(If the issue only occurs with certain plugins enabled, list them here.)" - type: markdown attributes: value: "# Additional materials" - type: textarea id: logs attributes: label: Support log description: "Please generate a support log ([Instructions](../wiki/Help#generate-support-log))" placeholder: "Paste any support log messages related to this issue here." render: plain text - type: textarea id: screenshots attributes: label: Screenshots description: | If applicable, add screenshots to help explain your problem. You can drag-and-drop or cut-and-paste images into the text areas on this form. Use either the box below, or a different field if appropriate. placeholder: (Drop images here.) - type: textarea id: notes attributes: label: Notes description: Any other useful details regarding the issue, your system environment, or this report GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002261460766671100275570ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later blank_issues_enabled: false GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/ISSUE_TEMPLATE/feature_request.yml000066400000000000000000000052251460766671100315210ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: Feature request or idea description: Suggest an improvement to GSConnect labels: enhancement body: - type: textarea id: summary attributes: label: Describe your request description: > Is your feature request related to a problem? Please provide a clear and concise description of what the problem is. placeholder: I'm always frustrated when [...] validations: required: true - type: textarea id: solution attributes: label: Proposed solution description: > Describe the solution you'd like. Provide a clear and concise description of what you want to happen. placeholder: It would be great if [...] - type: textarea id: alternatives attributes: label: Alternatives description: | A clear and concise summary of any alternative solutions or features you've considered. - type: markdown attributes: value: "# System details" - type: input id: gsconnect_version attributes: label: GSConnect version description: (Launch "Mobile Settings" and select "About" from the menu) validations: required: true - type: dropdown id: install_source attributes: label: Installed from options: - GNOME Extensions website - GitHub releases (zip file) - OS package manager - Extension Manager - other source validations: required: true - type: input id: shell_version attributes: label: GNOME Shell version - type: input id: distro attributes: label: Linux distribution/release placeholder: "Ubuntu 22.04, Fedora 37, Debian 11, Arch, etc." - type: textarea id: notes attributes: label: Additional context description: > Add any other context for your suggestion, including screenshots if applicable. - type: markdown attributes: value: "# All device features must first exist in KDE Connect" - type: markdown attributes: value: > GSConnect works in concert with the [KDE Connect](https://community.kde.org/KDEConnect) app on paired Android devices. Communication between GSConnect and the device is controlled by **KDE Connect**, _not_ GSConnect. Any request that would require features not supported in KDE Connect, or would require GSConnect to deviate from the KDE Connect protocols, cannot be implemented and will not be considered. All such requests should be made to the KDE Connect project. The requested functionality can only be implemented in GSConnect **after** support is added to KDE Connect. GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/dependabot.yml000066400000000000000000000003521460766671100262340ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: daily GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/labeler.yml000066400000000000000000000002571460766671100255410ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # Add label to new issues triage: - '.*' GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/workflows/000077500000000000000000000000001460766671100254415ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/workflows/gnome-3-36.yml000066400000000000000000000025361460766671100276650ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: gnome-3-36 on: push: branches: [ gnome-3-36 ] pull_request: branches: [ gnome-3-36 ] jobs: test: runs-on: ubuntu-latest container: image: andyholmes/gsconnect-ci:3.36 steps: - uses: actions/checkout@v4 - name: Prepare run: | meson --prefix=/usr \ --libdir=lib/ \ -Dgnome_shell_libdir=/usr/lib64/ \ _build - name: Uninstalled Tests run: | meson test -C _build --suite gsconnect:data \ --suite gsconnect:lint \ --print-errorlogs - name: Installed Tests run: | ninja -C _build install xvfb-run -a dbus-run-session -- \ gnome-desktop-testing-runner gsconnect -L _build/meson-logs - name: Upload Test Logs if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: test-logs path: _build/meson-logs - name: Test Build run: | ninja -C _build make-zip - name: Upload Test Build uses: actions/upload-artifact@v4 with: name: test-build path: _build/gsconnect@andyholmes.github.io.zip GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/workflows/gnome-3-38.yml000066400000000000000000000025351460766671100276660ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: gnome-3-38 on: push: branches: [ gnome-3-38 ] pull_request: branches: [ gnome-3-38 ] jobs: test: runs-on: ubuntu-latest container: image: gsconnect/gsconnect-ci:3.38 steps: - uses: actions/checkout@v4 - name: Prepare run: | meson --prefix=/usr \ --libdir=lib/ \ -Dgnome_shell_libdir=/usr/lib64/ \ _build - name: Uninstalled Tests run: | meson test -C _build --suite gsconnect:data \ --suite gsconnect:lint \ --print-errorlogs - name: Installed Tests run: | ninja -C _build install xvfb-run -a dbus-run-session -- \ gnome-desktop-testing-runner gsconnect -L _build/meson-logs - name: Upload Test Logs if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: test-logs path: _build/meson-logs - name: Test Build run: | ninja -C _build make-zip - name: Upload Test Build uses: actions/upload-artifact@v4 with: name: test-build path: _build/gsconnect@andyholmes.github.io.zip GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/workflows/issue-labeler.yml000066400000000000000000000006341460766671100307230ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: Issue Labeler on: issues: types: [opened] jobs: triage: runs-on: ubuntu-latest steps: - uses: github/issue-labeler@v3.4 with: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler.yml enable-versioned-regex: 0 GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/workflows/label-conflicts.yml000066400000000000000000000022471460766671100312320ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: "Label merge conflicts" on: # So that PRs touching the same files as the push are updated push: # So that the `dirtyLabel` is removed if conflicts are resolve # We recommend `pull_request_target` so that github secrets are available. # In `pull_request` we wouldn't be able to change labels of fork PRs pull_request_target: # GitHub documents "synchronize" as: # A pull request's head branch was updated. For example, the head branch # was updated from the base branch or new commits were pushed to the # head branch. types: [synchronize] jobs: conflicts: runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: check if PRs are mergeable uses: eps1lon/actions-label-merge-conflict@v3.0.0 with: dirtyLabel: "conflicts" repoToken: "${{ secrets.GITHUB_TOKEN }}" commentOnDirty: "This pull request has conflicts, please resolve those so that the changes can be evaluated." commentOnClean: "All conflicts have been resolved, thanks!" GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/workflows/main.yml000066400000000000000000000030201460766671100271030ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: CI on: push: branches: - main - gnome-43 pull_request: branches: - main - gnome-43 workflow_dispatch: jobs: test: runs-on: ubuntu-latest container: image: ghcr.io/gsconnect/gsconnect-ci:main steps: - uses: actions/checkout@v4 - name: Prepare run: | meson --prefix=/usr \ --libdir=lib/ \ -Dgnome_shell_libdir=/usr/lib64/ \ _build - name: Uninstalled Tests run: | meson test -C _build --suite gsconnect:data \ --suite gsconnect:lint \ --print-errorlogs - name: Installed Tests run: | ninja -C _build install xvfb-run -a dbus-run-session -- \ gnome-desktop-testing-runner gsconnect -L _build/meson-logs - name: Upload Test Logs if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: test-logs path: _build/meson-logs - name: Test Build run: | ninja -C _build make-zip - name: Stage packaged extension run: | ninja -C _build make-pkgdir - name: Create and upload packaged artifact uses: actions/upload-artifact@v4 with: name: gsconnect@andyholmes.github.io path: _build/gsconnect@andyholmes.github.io/ GSConnect-gnome-shell-extension-gsconnect-43258f9/.github/workflows/reuse.yml000066400000000000000000000006371460766671100273150ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: REUSE Verification on: push: branches: - main pull_request: branches: - main workflow_dispatch: jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: REUSE Compliance Check uses: fsfe/reuse-action@v3.0.0 GSConnect-gnome-shell-extension-gsconnect-43258f9/.gitignore000066400000000000000000000002311460766671100240300ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later _build *~ webextension/*.zip GSConnect-gnome-shell-extension-gsconnect-43258f9/.reuse/000077500000000000000000000000001460766671100232455ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/.reuse/dep5000066400000000000000000000013171460766671100240270ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: gnome-shell-extension-gsconnect Upstream-Contact: GSConnect Developers <> Source: https://github.com/GSConnect/gnome-shell-extension-gsconnect # Sample paragraph, commented out: # # Files: src/* # Copyright: $YEAR $NAME <$CONTACT> # License: ... Files: data/icons/* data/images/* data/metainfo/*.png Copyright: GSConnect Developers <> License: GPL-2.0-or-later Files: po/*.po po/*.pot Copyright: GSConnect Community License: GPL-2.0-or-later Files: installed-tests/data/* Copyright: GSConnect Developers <> License: GPL-2.0-or-later Files: webextension/images/* Copyright: GSConnect Developers <> License: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-43258f9/CODE_OF_CONDUCT.md000066400000000000000000000005271460766671100246470ustar00rootroot00000000000000 # Code of Conduct While taking part in this project, all that is required is to stay on topic. Note that you are still bound by the Code of Conduct for whichever platform you use to access the project repository. GSConnect-gnome-shell-extension-gsconnect-43258f9/CONTRIBUTING.md000066400000000000000000000142131460766671100242760ustar00rootroot00000000000000 # Contributing Thank you for considering contributing to this project. It means that you not only find it useful, but that you think there's something that could be done to make it more useful, or useful to more people. The goal is to create an implementation of KDE Connect that integrates with the GNOME desktop more than is appropriate for the original implementation. ## Code of Conduct While taking part in this project, all that is required is to stay on topic. Note that you are still bound by the Code of Conduct for whichever platform you use to access the project repository. ## Overview This document is mostly about code contributions. There are pages in the Wiki for [Translating][translating], [Theming][theming] and [Packaging][packaging]. You can open a [New Issue][issue] or [Pull Request][pr] for anything you like and it will be reviewed. ### Code Guidelines * Code MUST be written in [GJS][gjs] if at all possible Obvious exceptions are code that help integrate with other programs, like the Nautilus extension. * Code MUST run anywhere GNOME Shell runs It is acceptable and sometimes necessary to use resources that may be specific to Linux, but fallbacks must be available for other systems like BSD. Virtual machines may be supported, but not at any expense to real systems. * Code MUST NOT break compatibility with the KDE Connect project Under no circumstances may code break protocol compatibility or introduce new protocol features. Any protocol related discussion must happen directly with the KDE Connect team and changes or additions are subject to their approval. ### Code Style GSConnect ships with an ESLint file, which is run on all code by the CI and can be run on code simply with `eslint src/`. When in doubt, copy the existing code style. ## Developing ### Architecture GSConnect is composed of three relatively distinct components: * Service (`/service`) The service runs as a separate process in the background and does all the heavy lifting, including connecting and communicating with remote devices. It also exposes several DBus interfaces. * Shell Extension (`extension.js`, `/shell`) The GNOME Shell extension controls starting and stopping the service, and consumes the DBus interfaces exposed by the service. It also helps GSConnect to integrate into GNOME Shell. * Preferences (`gsconnect-preferences`, `/preferences`) Unlike most extensions, GSConnect has it's own process for configuring the service and devices, which also communicates with the service over DBus. ### Building and Installing GSConnect uses a [`meson`][meson] build system which can accomodate system or user installs. Typically, GSConnect should be developed and installed as a user extension, while support for system installs exists primarily for distributions that want to package GSConnect. #### User Install When installing as a user extension, GSConnect will try its best to detect necessary paths and automatically install required files required for the service to run. The example below will build a user extension ZIP, then install it: ```sh $ meson _build $ ninja -C _build install-zip ``` Use the `make-zip` target instead to simple build a distributable ZIP, which will be output as `_build/gsconnect@andyholmes.github.io.zip`: ```sh $ meson _build $ ninja -C _build make-zip ``` #### System Install When installing as a system extension, the build must be configured to ensure GSConnect can function properly when run (details in `meson_options.txt`). Below is a typical example for Fedora: ```sh $ meson --prefix=/usr \ --libdir=lib/ \ -Dgnome_shell_libdir=/usr/lib64/ \ -Dfirewalld=true \ _build $ ninja -C _build install ``` ### Typical Workflow The typical workflow for developing GSConnect will mainly involve working on the service. First build and install the extension: ```sh $ meson _build $ ninja -C _build install-zip ``` Next restart GNOME Shell and enable the extension. While developing you should enable debugging output and watch the output with `journalctl`: ```sh $ dconf write /org/gnome/shell/extensions/gsconnect/debug true $ journalctl -f -o cat /usr/bin/gjs ``` After making changes to the service, you should run the `install-zip` target again. The service will automatically restart and there is no need to restart GNOME Shell unless you have made changes to the shell extension: ```sh $ ninja -C _build install-zip ``` #### Preferences When working on the Preferences application, you must close and reopen the window after running the `install-zip` target. You can use `journalctl` to watch the output or if it's more convenient simply run the application from the shell: ```sh $ cd ~/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io $ ./gsconnect-preferences ``` #### Shell Extension When developing the Shell extension, you must restart GNOME Shell after making any changes. Note that the `debug()` function is not available in the Shell extension and you should watch `gnome-shell` with `journalctl` instead of GJS: ```sh $ journalctl -f -o cat /usr/bin/gnome-shell ``` ## Questions For general discussion, there is an IRC/Matrix channel for GSConnect: * Matrix: https://matrix.to/#/#_gimpnet_#gsconnect:matrix.org * IRC: irc://irc.gimp.org/#gsconnect If that's not convenient, discussion can happen in the comments to your [Pull Request][pr] or you can open a [New Issue][issue] for more public discussion and mark the Pull Request as a fix for it. [design]: https://wiki.gnome.org/Projects/GnomeShell/Design/Principles [hig]: https://developer.gnome.org/hig/stable/ [translating]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/Translating [packaging]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/Packaging [theming]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/Theming [issue]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/issues [pr]: https://github.com/GNOME/gnome-shell/pulls [gjs]: https://gitlab.gnome.org/GNOME/gjs/wikis/home [meson]: https://mesonbuild.com/ GSConnect-gnome-shell-extension-gsconnect-43258f9/LICENSES/000077500000000000000000000000001460766671100232515ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/LICENSES/GPL-2.0-or-later.txt000066400000000000000000000416711460766671100264650ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice GSConnect-gnome-shell-extension-gsconnect-43258f9/LICENSES/MPL-2.0.txt000066400000000000000000000405271460766671100247470ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. GSConnect-gnome-shell-extension-gsconnect-43258f9/README.md000066400000000000000000000070101460766671100233210ustar00rootroot00000000000000 # GSConnect [Get it on GNOME Extensions][ego] [Available in the Chrome Web Store][chrome] [Get the Add-On][firefox] ## Overview [GSConnect][ego] is a complete implementation of [KDE Connect][kdeconnect] especially for GNOME Shell with Nautilus, [Chrome][chrome] and [Firefox][firefox] integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows. With GSConnect you can securely connect to mobile devices and other desktops to: * Share files, links and text * Send and receive messages * Sync clipboard content * Sync contacts * Sync notifications * Control media players * Control system volume * Execute predefined commands * And more… Please see the **[Wiki][wiki]** for more information about **[Features][features]** and **[Help][help]**. ## Project Status GSConnect is now under the GitHub organisation [GSConnect][gsconnect-org]. Please note, this project has migrated from a developer-driven model to a community-driven model. This means that GSConnect does not have dedicated developers working on new features or bug fixes. Instead, the project relies on contributions from its users and distributions that choose to package it. If you would like to take a more active role in the development and maintenance of GSConnect, you can start by [triaging new issues][issues], [fixing confirmed issues][help-wanted] and [reviewing contributions][needs-review]. If you need additional permissions, you may request them from one of the [current maintainers][people]. ## Nightly Builds For early updaters of GNOME Shell and those that wish to test the upcoming version of GSConnect, there are automated builds available for [download][nightly-build]. See [Installing from Nightly Build][nightly-install] for installation instructions. [ego]: https://extensions.gnome.org/extension/1319/gsconnect/ [chrome]: https://chrome.google.com/webstore/detail/gsconnect/jfnifeihccihocjbfcfhicmmgpjicaec [firefox]: https://addons.mozilla.org/firefox/addon/gsconnect/ [kdeconnect]: https://userbase.kde.org/KDEConnect [wiki]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/ [features]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/Features [help]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/Help [gsconnect-org]: https://github.com/GSConnect [issues]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/issues [help-wanted]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 [needs-review]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/pulls?q=is%3Apr+is%3Aopen+label%3A%22needs+review%22 [people]: https://github.com/orgs/GSConnect/people [nightly-build]: https://nightly.link/GSConnect/gnome-shell-extension-gsconnect/workflows/main/main/gsconnect@andyholmes.github.io.zip [nightly-install]: https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/Installation#install-from-nightly-build GSConnect-gnome-shell-extension-gsconnect-43258f9/RELEASE_CHECKLIST.md000066400000000000000000000015121460766671100250360ustar00rootroot00000000000000 # Release Checklist ## Preparing for a new release - [ ] Bump version in `meson.build` - [ ] Bump version in `data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in` - [ ] Run `meson _build` - [ ] Run `meson test -C _build` - [ ] Make a commit and push it ## Release: Github - [ ] Run `meson _build` - [ ] Run `ninja -C _build make-zip` - [ ] Tag a new release with notes at `https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/new` - [ ] Add `_build/gsconnect@andyholmes.github.io.zip` to the release ## Release: EGO - [ ] Run `meson _build` - [ ] Run `ninja -C _build make-zip` - [ ] Upload `_build/gsconnect@andyholmes.github.io.zip` to `https://extensions.gnome.org/upload` GSConnect-gnome-shell-extension-gsconnect-43258f9/build-aux/000077500000000000000000000000001460766671100237365ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/build-aux/ego/000077500000000000000000000000001460766671100245105ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/build-aux/ego/mkzip.sh000077500000000000000000000022731460766671100262050ustar00rootroot00000000000000#!/bin/sh # SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later export DESTDIR="${MESON_BUILD_ROOT}/_zip" ZIP_DIR="${MESON_BUILD_ROOT}/${UUID}" ZIP_FILE="${MESON_BUILD_ROOT}/${UUID}.zip" # PRE-CLEAN rm -rf ${DESTDIR} ${ZIP_DIR} ${ZIP_FILE} # BUILD if ! ninja -C ${MESON_BUILD_ROOT} install > /dev/null; then exit 1; fi # COPY mkdir -p ${ZIP_DIR} cp -pr ${DESTDIR}/${DATADIR}/gnome-shell/extensions/${UUID}/* ${ZIP_DIR} cp -pr ${DESTDIR}/${DATADIR}/nautilus-python/extensions/* ${ZIP_DIR} cp -pr ${DESTDIR}/${LOCALEDIR} ${ZIP_DIR} cp -pr ${DESTDIR}/${GSCHEMADIR} ${ZIP_DIR} glib-compile-schemas ${ZIP_DIR}/schemas # Stop without zipping dir, if requested if [ "$NOZIP" = true ]; then echo "Extension staged in ${ZIP_DIR}"; exit 0; fi # COMPRESS cd ${ZIP_DIR} zip -qr ${ZIP_FILE} . echo "Extension saved to ${ZIP_FILE}" # INSTALL if [ "$INSTALL" = true ]; then EXTENSIONS_DIR="${HOME}/.local/share/gnome-shell/extensions" INSTALL_DIR="${EXTENSIONS_DIR}/${UUID}" mkdir -p ${EXTENSIONS_DIR} rm -rf ${INSTALL_DIR} unzip -q ${ZIP_FILE} -d ${INSTALL_DIR} echo "Extension installed to ${INSTALL_DIR}" fi GSConnect-gnome-shell-extension-gsconnect-43258f9/crowdin.yml000066400000000000000000000007601460766671100242370ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later project_identifier: org.gnome.Shell.Extensions.GSConnect files: - source: /po/org.gnome.Shell.Extensions.GSConnect.pot translation: /po/%two_letters_code%.po languages_mapping: two_letters_code: nl-BE: nl_BE nl-NL: nl pt-BR: pt_BR pt-PT: pt sr-CS: sr@latin sr-SP: sr zh-CN: zh_CN zh-TW: zh_TW GSConnect-gnome-shell-extension-gsconnect-43258f9/data/000077500000000000000000000000001460766671100227555ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/application.css000066400000000000000000000041761460766671100260020ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect * * SPDX-License-Identifier: GPL-2.0-or-later */ /** * Chrome/Firefox buttons in Settings Window */ .badge-button { border: 0px; padding: 0px; background: transparent; } /** * Placeholders */ .placeholder { background: @content_view_bg_color; margin-bottom: 30px; } .placeholder-image { margin-bottom: 16px; opacity: 0.5; } .placeholder-title { font-size: larger; font-weight: bold; margin-bottom: 3px; opacity: 0.5; } .placeholder-description { font-size: smaller; opacity: 0.5; } /** * GSConnectContactChooser */ .contact-window { border-top: 1px solid @borders; } .contact-list row { padding: 0px; } /** * GSConnectConversationWidget */ .message-scrolled { border-bottom: 1px solid @borders; } /** * GSConnectMessagingWindow */ .message-list > label { font-size: small; margin-bottom: 1em; } .thread-list { background: @content_view_bg_color; } /** * Incoming Message Bubbles (GtkLabel subclass) */ .message-in { color: @theme_fg_color; background: alpha(@theme_selected_bg_color, 0.2); border: 1px solid alpha(@theme_selected_bg_color, 0.2); border-radius: 5px; box-shadow: 2px 2px 3px -2px rgba(0, 0, 0, 0.75); padding: 6px 9px 6px 9px; } .message-in selection { color: @theme_selected_bg_color; background-color: @theme_selected_fg_color; } /** * Outgoing Message Bubbles (GtkLabel subclass) */ .message-out { color: @theme_fg_color; background-color: @theme_bg_color; border: 1px solid alpha(@theme_fg_color, 0.1); border-radius: 5px; box-shadow: 2px 2px 3px -2px rgba(0, 0, 0, 0.75); caret-color: currentColor; -gtk-secondary-caret-color: currentColor; padding: 6px 9px 6px 9px; } .message-out selection { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } /** * Must contrast with message background */ .message-in *:link, .message-out *:link { color: @theme_fg_color; } /** * Special case for pending messages */ .message-pending .message-out { opacity: 0.5; } /** * Error Dialog */ .error-stack-frame > * { border-top-width: 0; } GSConnect-gnome-shell-extension-gsconnect-43258f9/data/config.js.in000066400000000000000000000013161460766671100251660ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later export default { PACKAGE_VERSION: @PACKAGE_VERSION@, PACKAGE_URL: '@PACKAGE_URL@', PACKAGE_BUGREPORT: '@PACKAGE_BUGREPORT@', PACKAGE_DATADIR: '@PACKAGE_DATADIR@', PACKAGE_LOCALEDIR: '@PACKAGE_LOCALEDIR@', GSETTINGS_SCHEMA_DIR: '@GSETTINGS_SCHEMA_DIR@', GNOME_SHELL_LIBDIR: '@GNOME_SHELL_LIBDIR@', APP_ID: '@APPLICATION_ID@', APP_PATH: '@APPLICATION_PATH@', IS_USER: false, // External binary paths OPENSSL_PATH: '@OPENSSL_PATH@', SSHADD_PATH: '@SSHADD_PATH@', SSHKEYGEN_PATH: '@SSHKEYGEN_PATH@', FFMPEG_PATH: '@FFMPEG_PATH@', }; GSConnect-gnome-shell-extension-gsconnect-43258f9/data/firewalld/000077500000000000000000000000001460766671100247265ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/firewalld/gsconnect.xml000066400000000000000000000006701460766671100274360ustar00rootroot00000000000000 GSConnect KDE Connect implementation for GNOME GSConnect-gnome-shell-extension-gsconnect-43258f9/data/firewalld/meson.build000066400000000000000000000004351460766671100270720ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # firewalld Configuration if get_option('firewalld') install_data( 'gsconnect.xml', install_dir: join_paths(libdir, 'firewalld', 'services') ) endif GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/000077500000000000000000000000001460766671100240705ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/computer-symbolic.svg000066400000000000000000000051511460766671100302700ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/group-avatar-symbolic.svg000066400000000000000000000011121460766671100310330ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/laptop-symbolic.svg000066400000000000000000000015051460766671100277300ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/mouse-left-button-symbolic.svg000066400000000000000000000012511460766671100320200ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/mouse-right-button-symbolic.svg000066400000000000000000000012511460766671100322030ustar00rootroot00000000000000 mouse-with-smaller-scrollwheel-symbolic.svg000066400000000000000000000011621460766671100344300ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons org.gnome.Shell.Extensions.GSConnect-symbolic.svg000066400000000000000000000065011460766671100352740ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme org.gnome.Shell.Extensions.GSConnect.svg000066400000000000000000000433351460766671100334630ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/phonelink-delete-symbolic.svg000066400000000000000000000106231460766671100316610ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/phonelink-lock-symbolic.svg000066400000000000000000000111721460766671100313470ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/phonelink-off-symbolic.svg000066400000000000000000000071341460766671100311740ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/phonelink-ring-symbolic.svg000066400000000000000000000123651460766671100313630ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/phonelink-setup-symbolic.svg000066400000000000000000000127751460766671100315710ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/phonelink-symbolic.svg000066400000000000000000000065011460766671100304210ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/smartphone-symbolic.svg000066400000000000000000000077261460766671100306240ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/sms-send.svg000066400000000000000000000003001460766671100263330ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/sms-symbolic.svg000066400000000000000000000011721460766671100272330ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/tablet-symbolic.svg000066400000000000000000000007531460766671100277100ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/icons/tv-symbolic.svg000066400000000000000000000110421460766671100270570ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-43258f9/data/images/000077500000000000000000000000001460766671100242225ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/images/chrome-badge.png000066400000000000000000000072621460766671100272540ustar00rootroot00000000000000‰PNG  IHDRÎ:øfËtEXtSoftwareAdobe ImageReadyqÉe<TIDATxÚì]{pTWÿv³›Ç†w§b ò Sy:H)™é0ÕŽSÚ¨ÔΈíÈPkÅ©Z§ZLjXÆ@¤U¦6:Ö" "¯i”ÇR XK a7Ùd³ëý»ßæìͽwY`S¾ßÌe7wÏëÞûý¾×9çâ9w¾õ[$2‚Ç N¬lÂ8¹Ašèè¼F>|™0~¬Ü âxå6™Cˆ#q!Ž@ Ä„8G d _¶;::(§U6‹åd°Ãi§²²Rž¶àöÇãñ×ëMK¨Q6Dɶ@\5`$[œáºPÑh”Òj–ÆçóÉÓ¼7ˆ“mÌ”––R ¤$­òý‘u1•Ïï—'&ÙÄN >fÌu¤‹"ã(0â©Î«W%Îܹ1NIš–FGaQÑ-'M»aåΜ=—u}Ô …zT;ÛþãX¿¡Œ¨›nÿNmd2NAžçÆ*ÆÉĺuuuÝòqî|¥‘ž{þG®BïF:ÔÝ·ÿ 9zBµåÖÊ %C›j#ÔýôEªßþRÚc{íõ݉¿Íkl6Üêä@¦èííUs@NÄÎ Ì„h¹²6'ß<¥¾Cø×>òPFõËËÊhÓ_¥êª*Uÿfãñu_¤@ =KÞÞâì¢U+WFq˜NqR®&L‡X€òò2%\°¡V+€PL¢#ÇŽ+r­ZùU.OuU%­}tú„Vðs«“\¯¿n4ê4B Å $„ÿ››¾£ÚŸ;g6=øÀê¤ñ ;¬ßÜ9µª]sp¼Ç©ººRc‰Pç#Ó¦&‘ õÙúÁÒðøŽ;Aõ ¦Å˜06”EŸh׃ëDß‚<œÇÉÒ0)æÎ®5ŽÙJàO67+á€r,±ïO@¨P(¤, ÈöÜó/h±C()–i ¬L&CÆø e@0ÝúÕ7ì0ˆQ¥H‹auËTŒÓÞ©úC?Ê‚z[ Ú@*&”—AVœã¶ë·ïP})ËiÔÁ$ÊSâä x˜PO"È m‹ï Dz¥KÔó°0ªžƒpA(A´ÅA?»ƒ‹-HhzÕ­J2m³Û—*i«ˆñ.[ú)UOw!/œŸ°,lµØš°D?Ljýº$ºÍ®Z~[›JCXp`¾I‘ÉÐü ‰²0©`p|ùñõJ!ðgζ8&ØM[ûÈš„{–°Z¦1P’]Ãx@<˜Kà:˜Xܧ:oX>‡þ¡ bq†@¹e†pCÃEÁñígžR‚ K‹2Á",»gI¢„ ñ…›%€ eABÝ2Á5drÂ…K&Im‚< îpÓäVKfo§$Ü8ô©®ÿØq|³8ùbmË@˜¬A°r± ÁQî”áV\ »9vv뜀rˆ`¡ ˆzYhuN=CûÃzq|d&R±þzÒ!sëR¥ÚD Û­ O®Wãe·Q¹¤ebqõ^µ)ªÉ¸"¶ôõõÒLœ8Qž¶ 'hù×…ÛgqÚ.Pÿ©¿SÿÙÓ4ÐÞNÑŽ+ Ð £ ?¿ ª† *kÈ?m:ùgÏ“'&¸3]µ˜áz„î¥ð±¿Æ‰â`m0Ç RÄ UäÌi ïߣˆT¸ðãT|Ïrò–UŒH7P ÄÉX {wÿŽz÷ï¥XO(AŽD;Ú¿¤7ÓI4êï1Ž?PñÒ{©ø¾ûÉq#‹8~¿?­E—‘‹ÿ¦`ÃVŠ^ºH…8QèÂÞÒ$“F£”N´Ãû©·é(^÷ù¦ÏÒFq°mZß:m‡ð‘CÚÙ ¬…׎CÌJÌátlèÉ®>ºñ½§)ð…µTüéûr~cx­OP"“…LgÒÆœÍâEóó>Ó„,!2~H1ëin'ðµqÚ ˜M·Mn×\e1˜ZO·n¾á¦Íã€4ÁúÍŠ4 RĬ~X–¤Ñʆ^þ9·þ8§cGÚëÆúåå5 ÒÁ8o.÷oQ)h}V>_!Ý·ÿ€ë m+!P>Õ¢Q\?îC:ÀýCº‘õo^±íÖˆ÷Dr€IcoalÜ´”¤q*k~>@Õ“©xùÊaZÐ`ò“5"kmT¨'4â4¤¹ÔçxBÛ³‚ÀµBóëó9¸¹^̉{gÞÓõj :Ù|JõÅ+¶aÁFÂ\QΉ3p±Õp϶gNWÒ¤Ž‡B/דoÒSÆ<éh[€°®µ1ùiç¾@kòªe®Á€0€x<ˉGÔEYÔa÷…ë &b±h“]A}›ƒ^‡ë™ëë:“Æ  âè¤HhCx™8aUb'»:øãÑ}â r!µU˼\Ö2™8S“¬‰¹´èTBèX0ÙõBœÃÊ}YÊ@©è«ªÑ§>6^ýmíVîî‘N0¾n¾À/êfÛ?V[`Õ7Ʋ¼ÞepŸÐ¶µŽukEÞÅ8¿|ç0m¨ :Ç1v±N, Ò8X3ÄS=¿Ý™}B ¯dâ* ,C­œŽ¿ MÛÓ£«ª™˜[ê^ˆot«Tî’ua(Úcái¸–í }>AhZ”Åï(ÇA¸uœè ÖÅ´2ÍêíqL\Êc[bCAµŸGÛ¦€¿Ñ_ ¿u êB¨16(,;bë«ÊŸÜÊYûÇ3âóLVLÓc0ï÷™ëÍ„9³8Ýý=´åÜ©+à£Æ)£iuK—»…‰óÖ¡hÞ|òϘEåï3ÏGƒTÐóùƒMä‰\q±:vmÈ*Šú*èVBÃDÜc3¸ê{\ؽAL¡ÖÅ7ÂácÓ›òõ㮎)(kT›8Píq¸‹¬y]鶨ƒÇ‚ö 4vZŸ]+Îl4 ®DùxÛ„ŠG —‡…¤¹H»ÞŠ€ïn(ŒÍjuðúÀohï[¶hìÞ‚$(Ïí¨®ñ­¬hر5µÞ@|®Ï´ò+ò8¯µ5QW¿©efŒ£å­7hTÔÕ½*½ÿóTbôV×Ê´:£=J…×wSÑÕFòd2Ûòhxl-OÁµW)X¾.ãkÐÝ™tor*ó¯ÿއZ=§*éwôÉý[ÝDÄ<ö²3AxÎ ‚Z%t\15þe%X+#.à¦L T÷L·lúØìƒ³jn™4ŒÅîx|ºwÀn¦™NO^=žë}D9#ÎïÛÞ`‡‰ºýª«O›Úm ¢Œ{ú»ä«™LWº‰ÿì¡Ó—=t%n¤J‹ˆæMŠÑ'¦ÅhÆÄ4P2“ŠßÝLáVgK£÷‡0–uY]ǰVÉÙ!»óé’ìâŶ!q•¦å&–OPŸÐ¸zÐΖÂIÈÙba‡«ýùÙ‰qY3nÖ¶­J†ëñظ\(lªÓßs [ç,gKÒ½qSL2Žõ1å:S—“çR¨“ÞHÃØ3iµŒ+´cÆ~í)Eš¿œõÐc¿òªÏ+šg 2ÎmzÝK;Žzi põVÇq¾ó¶ŒM·ªp™xë'&x®H'~gëkÆŒýóXpÞNÑpÖ’ßÿÀí=ñõo$½Õ'o,NSÇyW‹”ÕùÉ¡ÿ%k]Ã=óOŸ¥È²Å°4sÏ¿íþ‡Ç8祇M¢ð„ÕTÜÞ"Í·:½oQày“8»†„5p6‚ceÑ´,4$û噎cá×Xé‚êdÑ¿ß ™„µñÕƒ K¦ˆbÜ ~ÇEz_è_Ÿ\ÅõX]X´… ^/«ï+27V&ÜH÷NýsLcw?¸ëý°Æ{ÃEÖûqðn´H$¢¾o|s§ãØkøMt¯ï0™*¶½B¡‚RúŠai‚awÒèáÑ3ŸÒ̉1uáKä¼ëJ R<“ºïÚ”ø{üøñ_#„œN¸J Ú̹°èç0IjýÝ:ïã4£ /[<«{Ä™/}EÅPs-zœ_¯Ÿê<ωà°•ÓÇ¡§¶æq¬×nm+Ùõ£Ä8Üú·»7vcÎÖµvöãä„8ÛLo´Ÿ·%ÁûƒjØwY% =ƒ›ÖØä¡ßüÍ“aø÷Oñκ%Qeq ¯ïr'¶%nݵš—†EÀ‰89‰qM‹i¡Œ.Êÿ-õÑ«SǨ³ˆk`yN_¦ŒH£ÜÁV“h‘’™)­ Ç9ÁÍBÖÄÑÿ‹Ž·¯_²×ã/Ü6c¬"£µÃ3„`)æE)ØË#.µùÕ}r4ÿND ¸%Äa7M'ˆõÐQW;Á1#K‘ ×-5©Võ÷÷Ë“äq°‡Éswù‡SvŒ „NV«35e±„…q³2ú‰ŠÑqÌ)1àò:]ùO©¹FÖ…`oºéîî¦ho?yúR™ÚÒ÷Í …è£wy蟼 ÙOµGœ˜b-ŠRqç)ŠöDÈ3ø5ê+S Œ¢¢"*//—'-È)²Îª w*r–U$ÆB@ˆ#q!Ž@ ÄîP W¯Ë2$ΦŽÎkr'‚ ðÛ«>!k¸ IEND®B`‚GSConnect-gnome-shell-extension-gsconnect-43258f9/data/images/enter-keyboard-shortcut.svg000066400000000000000000000433331460766671100315350ustar00rootroot00000000000000 image/svg+xml GSConnect-gnome-shell-extension-gsconnect-43258f9/data/images/firefox-badge.png000066400000000000000000000136561460766671100274450ustar00rootroot00000000000000‰PNG  IHDR¬<ûWQ›bKGDÿÿÿ ½§“ pHYs  šœtIMEá 2×ßg;IDATxÚí{\Õ}ç?çÜGwO÷ô¼_i¤‘4HèHƒ„ˆ0‰ k­1‡Ê:ÖšÀú ŽRI¶¶âJ‘*{Uq´ñ©,®Ä»^¼`³,•1à%Æ+ „$$$ž3i¤ÑHóž~Ü×9ùãv÷tf¤™ƒdúWuêÞ~ݾçÞOÿÎ÷üÎ²•í:2Qü ú©#_¶·‰òå)Û§hcÀào‡_öòeÀV?uäÛÀ_–¯SÙ®AûÎÐã˾U¶ú©#ÿ x©|]Êv Ûæ¡Ç—½$sþ´|=ÊvÛV€<°kÊ×£l׸­)¶òz8ãùgxdÞ D¤S¾}Ÿ=«0¯—³H‡?Zð2›[ßäB¶ž_\\_¾…ŸA“×ˉ~¡þM¾²àZ“ ¾¾ðETt—ï^ØOÞ´(_ <ƒÀ3 <›À·¥%IÒæ÷æþœÚ˜‰|aÎ9þýâghˆ\*:.ßÍÏ€}ì’@(…ÔŠÀ˜úÐÚÕiƒ k¸6JEP† † †[­–0\64ìb}ãa„\B`˜sùƒ…GȨóÝ£ÿ†1'ÎÍá=–³|WËÀN×]j6ÙË™šz¶¶_þz*@]Ôxƒ&N&Š2c± jm‚4 CȤƴߦ*ª+÷«ØÖí8ˆæyÞ¿ÌÞÁßô?Á ѧÇYD üÀÀ ,Ðc ]v)´ºèZ`U¶ò•…Ø5¶—_ î#×íÉ5l^RM{m´ð\ç@–ïíéc{wŠ_~±•ÍñË>wÿ³'yåáES÷ýÞ¿õÒx,yªãä­æû0øØüÝ®^ž<4XòÚ³w·psKœ%Ïœ`K[œm÷¶]öù׎ñð›çËÀD‡Úá~ªdš&cŒˆá"Lf¦S!Í€¨åµ=°á >À‘ìc¼‰×€¹2 œN·Ò; zhšàYóûù­dAà‘y»90ºŒQ5û´Þm·Ö³euÏîgõO; ÏÿãºFuUj¾ÿAaÿè#‹9p>5%4Åð8[»ÿÙ“ìvËv2«ÎŒÑî Ñhd°DPÒË/3×û7Ãç}ÏÆI%ƽdØ<¬-tjÙ}iJyœí:yXüîœã|¡ñÝY×iK[œ-«ø»]½<º³¯äµGwöñâ…L¹}¾µ<Ÿß{›•ƒ=H¡"×a2&•ƒ6,‰¬Ë©Ø,_§Mt“d[gAÂHð%¾Š²ãÜ=\ÊÔBÐ AO¨k'…U ÷“¶äÁ9û¨7fU¯»æÆI¹ÁeÍoÙ®sIpçñ£Ïœß0ãzuÔE95Ö{7vT3ØQ=¥>ý8í›k›ùæÚæËžïóJOÔÏ[_íf{wê³ l"æwß;@}jij„Ð1V=!T•{¬BCƒ¢–›ÇжÎòÜð¤üXaO†~pòVWfMí!P'sµ—C«Çá­0îoÜÏ+neD}ô)jGYLcº ÊO²Cs¥NWYÃ^Ån9ÕÍš“'ä@Í÷š¥¥mn_zZ=þ­à¢WÃkw‘ö¢àKð$¸¨(ï ÞÉw=ÎÉáyà{ ¼ÞœTn›¯7` ?`ͱ.*,"GÈÇOE1´º4¶š/>HÖ0Äyyd=vú/xº÷a´øzX×â<{æþzßV>hoœãàvƒò.6Kµ‘æó5‡3ªß[gS4&,¶v$Ë”ü&H‚Æaõ\@‹)Þ ôp‹_ÒtqŠ—†îâÙ‘¼¡×3h7C,¾ dX#Ìâ’I~tz ©tœo¯ø¯ÜØx üS`dÀœDÆ;_EZvuü #ÃH0}Y°½;ÅÃÝ£üÕÝ­´%m¶î½T¦åzÖr}"®‡œ* U‹\™êš×­eüÅ¥opÆj'ˆ%Âè€ëçÂ[9Ù t¨W•!ATòB÷ƒÌÏvñŸnù¢ øgÁwBhEEIÄ@hMGômv‡23Ó±›^=˶[ëÙtC5[V7žO¹Ï˜²Óu-hȉ®ÏüÀå$²^‰ëÔ[4j•Û×*LÀ— 4Rk¨ ıd€v]TþXZC Á”¡g5d­Èå'6ñPÏ+Ü‘>ˆhü‹à¹`µ‚ˆ—„¸*„Ãr£“S4“¦bFõܺ÷Ò½ët£3˜ ¬âˆ©Þ¿½;Åö)Þ÷™ö°õÃi*Ç´hZµýE)48®ÉH6Š×/è“ öÈvš96< ° ƒðžÖ:‚fêP ˜2L=̧!†½;zÇšøÿîÍÜ>ö>⬆€aðkˆœšòGñjQžÄª–Éý܉:s™lqÛà[«ë®m àDmºæÔâ* ?We¢r^6¯a)dýI|7Š;V‰7–ÀK%RQTÖ$È(G¢râ¼¼gOý|=ï‹óˆ:©PËfÇ0ӗظäVÍ=p'ìeÐjð\†\›ÝÎÍh1ó9^Å@ì?z›ÎŒ{uóg§c‹P™Þ­èÌX¦k7TYWüîb;32®Ñ§ŠVÔG ¾·§¯¤³6=ý©H¤äd[=)#¤CO«M|e`*©¤'–_ |øAœtªßر FÜEÆTÐÔ3ÑÓŸ°À©uœ®¯¡úâ(^`á–´´‡¡$Ò—àà)„+1-æ/+ùYçRål^Ò*þjÁíû.:`ßXÓÈÛ;º¯]`µi²wå\Öè¢"“Å ¬ÀƦò1¤Bx áH0ŒÂÜ.ihî›ÓEmÔåõîôµ’öÌ2ßC ?k¸JL4–á–¥'¸÷öý¬]ÖáyÑLN¯RkîÉžômœ5Ϫ~÷NðXW²¯-NÎhê«7wTò¹éØÄÎÓ'Bðvõ¤ Þv2°úƼBkñÀÒêI]We—Ô9¯Ù¯dß;<ȦªiLX4&,]^}í pja=o/›KË;CØ~KzX2Vª!ÂÓ§hB¢ÔH¡¹½¾‡z94ØÄ¡þfº‡jéÏTà)èI¦i›ÓÇMÝÜÜq–dÜ $aóîç¼+EÖ/Ú?ZÉÅʨ˜¹wÝÒŸRÃMf÷´WN Ø­I¾zS]‰—|ñèÕg0lnŠñ[êJF¨^;>TðêW“$;Ž <áÊæ8;î[‡ÍŸWqóšýjzø™÷û ÃÖùÛ5 ¬ð«ÛÚ¹éøy– ;X†‹D0…Ê } n.+Ç»…€ª˜ÏÍÝÜÞÖEÖ¤xR`F}âq—XÜÅ´r‰àãSm4¥ñ×"`µ}²ŠT’®¶Æñüƒ™t¶ŠÂUSå ç¬lŽO) ®”W°«{tJЧšp˜o®ÿÃî¾i×§ØæeÌ+mSG8ž?Ü?íøò“‡/[läš k•xÙE ¼q{ÃÄqüŽÁQÜÀƬ06ë-.fX2a1I"h2³ÌÑIQ)LO‡ æÃWNN·:9o…±\Ð>ç#vÍ_ÌþØÒYÕ©8\õÏ]£“¾çé#%“â±éØkLJ&M ¹š½ß›âßîèž‘fîÌág¦ÛÕ=zÙZ WýAìéã“°gm-Óâ—k2ïÂ÷ïÿSz2À2gÓ‚F¸¹ÉŠÅoââ¹õðr [ªÐaC»N<yhÃØ¯Ðš sl~\{ý²~ÆÕÙÚ‘,iŸ;12%;OzÊTÂUF¬úÆV`…”[ÚÊÿ¼!¸«ódèE~ñйo”N˜#r™]%RÀSá¶X˜ª$ÊP˜ý˜› žÑ¿ˆ¯æéÚͼQ}'˜eXËÀN ZƒKæòOQ‹¡·"ÜwäC„7¾Ú F  …‡Q4;¡à]} ^®—ÅërÁªïYì‰ßÀ µx±þnúâ-ˆYDÊö6ïi{4óOÕwÒÝTÍæ}Y:Ü[˜û• X:„Vª\gÉËÁjÁj^.4ql.ÕðóºÏñý%_äDí0-ʨ–´B®Iòâ=·r¬­M{ßç·O¥É…9`Êh ¥B/ëID ¬!°ÚÐxB3èš ¤c¤½ v5¯æGKîçDãâò,û1™eóÁ’…œjmbçñEløðCîè9ÅüL?1!–áaj?L˜ T¨as°Š"滛J’­¬æõ¶ÛøÉâßád]{Ù«–ýø%‚›¬dת弻d z{Ysú·œ?ÍÒÁó4g‡Hê QÅVfn!7åC`¸–d "ÆÑšÞnZÌ›sWq¸a+ÃZö× ®Ÿˆs|ÑBŽ/hã§Y‡ºÑašFh¢)=LÒMcj%Á±l*œOÔÐ[YËùd=éXmZå;WöW0-¼„Eo"Áùæ9¹µŒ·ã„çd¹ç_¶`GÉýý§paúJù¯ŠÊ6©ÁxòË»åëQ¶kÜö»­|=ÊvÛ¶°C/{ øNùš”íµïä- U?ud3ð'Àç€xù:•íS´ð°-kÙÊvÝÙ¿@ùœZœ‰IEND®B`‚GSConnect-gnome-shell-extension-gsconnect-43258f9/data/meson.build000066400000000000000000000043611460766671100251230ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later subdir('firewalld') subdir('metainfo') subdir('webextension') # metadata.json configure_file( input: 'metadata.json.in', output: 'metadata.json', configuration: extconfig, install_dir: extdatadir ) # config.js configure_file( input: 'config.js.in', output: 'config.js', configuration: extconfig, install_dir: extdatadir ) # Desktop Entry desktop_file = configure_file( input: app_id + '.desktop.in', output: app_id + '.desktop', configuration: extconfig, install_dir: join_paths(datadir, 'applications') ) desktop_prefs_file = configure_file( input: app_id + '.Preferences.desktop.in', output: app_id + '.Preferences.desktop', configuration: extconfig, install_dir: join_paths(datadir, 'applications') ) desktop_utils = find_program('desktop-file-validate', required: false) if desktop_utils.found() test('Validate desktop file', desktop_utils, args: [desktop_file], suite: 'data', ) test('Validate desktop file (Preferences)', desktop_utils, args: [desktop_prefs_file], suite: 'data', ) endif # Application Icon install_data([ join_paths('icons', app_id + '.svg'), join_paths('icons', app_id + '-symbolic.svg')], install_dir: join_paths(datadir, 'icons', 'hicolor', 'scalable', 'apps') ) # DBus Service dbus = dependency('dbus-1', required: false) if get_option('session_bus_services_dir') != '' dbus_dir = get_option('session_bus_services_dir') elif dbus.found() dbus_dir = dbus.get_variable( pkgconfig: 'session_bus_services_dir', internal: 'session_bus_services_dir', default_value: join_paths(datadir, 'dbus-1', 'services'), ) else dbus_dir = join_paths(datadir, 'dbus-1', 'services') endif dbus_service_file = configure_file( input: app_id + '.service.in', output: app_id + '.service', configuration: extconfig, install_dir: dbus_dir ) # GSettings install_data( app_id + '.gschema.xml', install_dir: gschemadir ) # GResource gnome.compile_resources( app_id, app_id + '.gresource.xml', gresource_bundle: true, install: true, install_dir: extdatadir, dependencies: [ desktop_file, desktop_prefs_file, dbus_service_file ] ) GSConnect-gnome-shell-extension-gsconnect-43258f9/data/metadata.json.in000066400000000000000000000012611460766671100260350ustar00rootroot00000000000000{ "uuid": "gsconnect@andyholmes.github.io", "name": "GSConnect", "description": "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. It does not rely on the KDE Connect desktop application and will not work with it installed.\n\nKDE Connect allows devices to securely share content like notifications or files and other features like SMS messaging and remote control. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows.\n\nPlease report issues on Github!", "version": @PACKAGE_VERSION@, "shell-version": [ "46" ], "url": "@PACKAGE_URL@/wiki" } GSConnect-gnome-shell-extension-gsconnect-43258f9/data/metadata.json.in.license000066400000000000000000000001651460766671100274600ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-43258f9/data/metainfo/000077500000000000000000000000001460766671100245575ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/metainfo/image-01.png000066400000000000000000007275331460766671100266060ustar00rootroot00000000000000‰PNG  IHDR€8èÓÁC—zTXtRaw profile type exifxÚ­›gr%9v…ÿcZ¼Yl„v åë;Ȥíêž…X]Eò™|Èk޹@›ý?ÿ}ÌñU\®&¦RsËÙò[l¾óCµÏ×óÝÙxÿýx¨¼þxÜ„zµÖóPà{xžÈûùî:§¯7”ø>>~>nÊ|~ðõ½Ðû¾_AŸ¬Ÿß×Õ÷BÁ?»÷wÓÞ÷õøívÞ¿~¾—}/þû÷XÆJ\/xãwpÁòoÖ§Vjèüëùׇ ÝŸSüBýsìÌç¿‚÷±Òß±³ý}Eø cóû‚ü+Fïã.ýzüりÐ÷¹ýÏ'vwÝ~ÿú»sV=g?w×c&RÙ¼7õÂû/„2Ü·eþþ&~.÷OãOå'[dsðgל'ÚÇE·\wÇíû}ºÉ£ß¾ðÝûéÃ}¬†â›Ÿ7)QÜñ%´°(Hr2ÉZàaÿ¹w?·ÝÏ›®òÉËñJ︘»yüõÇüéÁÿËŸÏ £ÒuNÁlO¬X—WM³ eNÿò*âÎÓtã{ÿ˜Ï´~})± ¦æÊu»Ï%Fr_µnž¯K6š·e]YïŸXŒ dÀf’ËÎï‹sı’ŸÎÊ}ˆ~—’_ÎrB&9Õë³yOq÷µ>ùça …D¤C!5-t’c¢~J¬ÔPO!E“RÊ©¤šZê9ä˜SιdaT/¡Ä’J.¥ÔÒJ¯¡Æšj®¥ÖÚjo¾ ,µÜŠiµµÖ;Ú¹tçÝWô>ü#Ž4ò(£Ž6ú¤|fœiæYfmöåWX´ÿÊ«˜UW[}»M)í¸ÓλìºÛî‡Z;áÄ“N>åÔÓNÿÌÚ›ÕŸYs¿2÷ÏYsoÖ”±x_W¾²ÆÃ¥|\ N’rFÆ|td¼(´WÎlu1zeN9³ Ð É“5—”œå”12·óé¸ÏÜ}eîófRüòæÿ.sF©ûÿÈœQêÞÌý5oÈÚê—QÂMºP1µál;y[p®íä<A敹y1&ï óð ãÄÞÁÖçqÚñ±Œqj”:ÝØ>l› 7fÇð­œ\À>öÄpZrgµIöòg'âU»ŸÕóñgÏôʶk1‡ –QÎ.äì}/S³iöô^ƒ¥ß«Äß’V÷ux;Z4ãÇUêOáCƈô~tås©ï5‚OÃ'V—Ë"² ÌžÙè)¯ûÙõ¤6 í Ú¤–ï{&“7ð>é:aÌÁzj&Ú2Îv&ÎÐKÚ6.]†‚ZcQ F[9®@X¨gu=kYQÇ^õ¹åI`ø>¶ÉÃS¼vï4xã 29Z‹8zQê}-ŠËâ©•½v¦„Î>­”ìA—5½mîÅg÷mìÜÏXô„ƒötÁîý˹¿µ)BY»MçF‘tzRÃ/{·cæýÔŽ¦8­}by¿ÅµB –ÈôªEÍÏÜi KDz–oàÊeR>Àìú~Û9»ÉDZyøYâ¬k¢Bf"? e¦êƒðÎÉ&Ó\«y¨wÕØ›û?ØþQ‹ß^gþT‰óÀÊ=Tbb]uîP¥Ò vÕ÷©pôrzsD<Š{Ï7w¡”Ò¬@ E±c=ãÌöZq‡…n˜”Þ¤ªüª½l*ç©zxsBÜx2Ö>ù¿4ïâÜ”sœ½…®%•‰@ꬮ”Kõe×gkÀâŽa5‘„ÕRz^|E% tE¬5å*• <»Š‚ͱºuêŽ+Žf—³TÁsŠ•îï=ÌRCž€ž Õñ·ä°†;ŒpÆÅ¥…-.L’XÕW°ì¬P£»„°ÏܽŒ[hi7xxR:Ú\èTßgHs9’Öw>~çÐܸípÕ8Iù…^‚”—.è¹Eé*x¼‡Ò­:hZjHÝ{¦n¸ü±~R5¨ã¹J&¹€è½½­ËQs—é7Dš¬€#Ðe»¡ÇN xBKvÙ.¤ÍH†ÏBe Ê|ÜCQ›÷›R­»¸ÆÅœR‚îŇaKoª¥º œ~h°a.ÈØ z‘÷™ˆ@nLn•HÜÈhYà´9Êd““3"è-{mº‹ÆdM'u2¥ðƒ9ñ"N· ÌÙÄ{;‚ˆZí| ®îTý‰³Ž oCqf.ÊËB^÷"$µë;•ÁŽ*Lª‘,€.yÃe8p¾rã—Weq ²y2‘tw)?B=ô9­N„&š»UFÖÊp—I—`à³¢GäÙ.öÔÑk†}½1í~ÖJçf=‹W­¨U±›éÑ5Ÿ.Á¤Í>Ð$:û®rIß]ûycSøÚ1›<“ýüžŠób§ºv¯µ&8e©=¡,îØ÷„$ÛkÃóäIA–ZBuÌÀм¶<òŠÁ'¨ —3ÆZEãk-éaª¤ìR¯ì£ô”°×ȨÔÈ€2|jô ÏÈEP^$(#ž(2¡áÐX½‹z!õ'@Æ`Êe·Ç…Ú i‚-´5ÆIG(%:)ÐÒS$ m ´r½è(ŒñpcTî‰%…¬á€d j·¬ ˜rQºŠÑü0„ÔF}Òtʶ >%ó43*`»…¦ŽÍ'ìWIþ&˜–èJC¹Ei¢ûoAÿÓÌô>½$¼E+$ðrfT%pl#‹“pqP I ~Ê"pBäÉèÅ“×FnüIÂ'µ§é|÷ÈZÀÆÏãFiŽk5vy…“ ›Þ;EæsÕ„8µ‘tŸ6†±õiºÓ`eø¡Äò„Ç™wR¼P5qëK’*B9Õm+-½éF0CÜÐÔýhË…âŽÜBV, ˜ïZeÓáÈ ;!ý ^zVB³E©8üöÇâ]1ÎLš˜E.¢³;%CÕN6ŽƒO´ #V ÇÀ ¢Ò ât“Ä OƒÑò¤ 7u ï±3ÀÁå±JïZ2Bƒ‚ôFBc(2DÁòIÜ"b¶Œ iQÚrU|:t5Ž‚Èè¢Æê¢, ƒÐÒ”‚f&1FWŽÜFFfIò¼ê « ×Eeàè®Ó|&>hqÒðä|-'QTƒ  ÂÊ™"šƒ"C|MíÂäJ¬v·¶­±Á‚¡<~qj|RwT*f—Ø5éÊ7P¹Ý®ãPš#a¨ò¼ˆHå*9>Ø4¥ÐOöçwóû({µ÷Bnr‚²ϬÏô©ŸÀjFM4!?ÐHWf=´Ü«Ä)ð±w±È÷úSãY©TnoØÞFS-€]ˆÝÉIz·5ЬȎjw7‚*E[ …0"·!…—åC mµ-À×?PIï°êF°ŸDä§…n8©’3»póP*˜Ú»ž¾’3ÜiáÆÍ૜ì8FD\ò²M¢Tˆ<-{E Ëâ> ´H@p®FL †GfqVº;‘nÊÏÇ|°;¸Çƽš:*€`ù”>»Izð)üG£O…ÀJÌf 2뉕(¹¾[«]·x–bÑZTöâ†xŽ¡übÕéˆo¥òvtø¦€áÈlÍÌûeô)ðpò{“~Mr –3ÏãÖ—^$WÛ,o” T;‰jn€©$öÝkž$¿RD¶&,€!­Zp XêÔ¸ƒ:…Šxú…êjÜà«åà³,qÏ"ÿÛ4·[ÑT‚'pin j°BÑ)jam ßøôh,;ñÓ‚´µÎ €Í,ÍÀwàè@ÑX™*ø[u3N¨’¾MÐer'–Ýš‚•1tþ¢çÜÐ Dê¥ý!á3íÒÐi<ìõgD‘|s_2õøžX HÐTÁb“[S+åCQóD]uŠË'´Œ%¸4”4Èd„fzP–›hCóÐ~üpÚ²&iJCÌÆ„*±*ß*@šÔž>QU¸:bš ž™Žm+gÖ ËRHËC°Ýe«\b`A£â©mPVûBËQQÒ0Ç…©ù;ÂUç˜OVl‹‘²îæ…žî$fJ2°¶±xçûZbaËÇ‹Ÿ–‚Ðp7K0Ð$üÀ¤ÞÖ-%K°QKP¯/˜ ‹(€<Æ!Åh'.ºŒ$g ­‹ ‰¢:éÄxÄÁ’#M°Dx÷ªéãª{³æ¯wûófÉ×ã<éàŠ~ï2’è|´ÏÒøòòò"_Q¹€W´Š¿.®“÷P5zKÐã8Ü£›‘xû¶ÈœžW2/¾¦€h8H^›wîkIQ…˜_×±ŽŒ±d#ò$2Z½¹[Îlº~;6R+Zƒxâò¨,§ë ÖÁ¡j˜Â5(ìwÔ©GØi€±½m =’ã(ÀxB0sñ4¦Û¨.Þ-'ü Âa6âÆT%™6˜`8!û¥­”äb·2 î’d6S"ß)$“îÓgh ‚…)c­fsMwM‚Ÿp!ß`˜ÛÓ|'“šéË'V¶ßÒð^6æELƒæ-5?͆gLÙ%ð]0âšhéùÉ"Šm½s¼OkËçyâF±ò1¤›¿lÏÏÐ`µºžÖÜïO1$Ýx¼Pƒ¶ tžc®Y kÇ÷¨ŒûbÂñ¯×Ö°1H(gôm¡±>Ø…)átªeBÃ{DÎXÄ£=Ób¬H†5Í"‡5q18WZÛLuFC,~¸ øEãÜ4·Õ¤ÓªTÓDë ú7‚¡U =3[¨DS¹—æz)¾Oͪ©-¶Má§T=賌¦{#ï4 1oÝxoÚã×€{TÊĸDZ‡µ©ÕŠQáÖÛ.÷iç>Rñˆ»—{SZçM×l·BQ€{‰iJŒ÷ @@Ùô̸,÷ºñg£É‚«-+TæÂê ë?RHõŠúéw:;Qtt 4>£xð!fê—B4dÑ‘ÈÇ Í´êÊû¹~^¦[µ#¢É'î‰÷ëS&O¯±šEè’k>T ÙSj -ŸÚQXh½ÝâtHVêË¡‹éEÉ`<(R»- w`·5Ä6VÁxæÑ„O ÛP©bÈ…ýD…›–ÑÕV]šWcírÔúÌËŒ£‘§­Kž–Õõ¤s™˜²š*ý„“d™ˆ°9WyúpO 68buªÏS¬Ã¹c!ˆz€ÜàÑ–Ñ~àMƒTòØÕ¤ùª8D¶Æà‚Äf)=Ú±†ô¶X)ô[‘øYÊp%ÑŽgÀuÀkRû|µ;q ¹¡ ®¦é)wƒRžý}¤îq4À8Rƒ_å æ‘J¬KSS„7 †\¤$“vêGÄACùEsÑy·| D{hÃbùÇoí9u(ÝKÓf¨zÙ*ƒF¦G€Y<ú1‘=’¥þ ÁÁ}øÃåH±´[ÇytPoáAOIŒ(C&oŠ¢Á»µMzÁZ˜ {C ×ÓPVö^{¥˜‹&†q§\=|„2Jp°;”S0ŠÆÁ˜ø$¹ñ /)¼ä(<íj“ bpë‘­(=OfŽè«ßZ_gÕ;ˆ ®fûÓ >žŸM³Jާ'yØÌt¡8cÁyk:ü€`éZ!Ø ªL>0Bò9O9TDÿ£kKïMÂoz»ó’vÒ‹MpÉü®ðV>B¨0!Ѷä÷„§Q½çNÝ Ö•‘©!â¶™Zkç⃡/QÀ­Ó§ ^T­ª6fªKH¨Ë¤!‚ßZ¨4-¼ñ¦ÚcÂzáÄ&‚o„|Pœ,dTþÐÚÑ3HJìqþWÜÕÔ<4/ ¨Î4i `{R²J [wÛ¨:.LQ NƒáG=Õeþ\^Éý«DS9·GœÓQÓ#S@¹\!¤¤ÇAZá CÐ<9æ=hA°0Ð6ÑôÎ²ä¹wä1M'qT@Þâ3ÚšM‘Á¡é=¬†Ïê:ßqçL»ÂCÉWpDfâ1 6‡•w(Š·k´keΊdÛCÓ!¤ñ6–äÈ¢vmC岆¨j€çî®-_z,ï,ŸÓ´- Íqý¡2Q”Àö&/9ãNº–¨³,µ9¯¡QŒ–›€ û5"®  .ùINh–ŽÒ¥ÚŒO·§‘.Qw‚a…k ޏ!¾jTèÐö.ÝEl‡§=4l³F D]¥”[nºÔv;C<ðJÏp8Wßp.¬^›sR4ß~AÔ) PÀËŸï0ÿÆ[Pynh¤®ã†ðê‘f-ײ>ï3σO wâvò¦U¦Ú”¤á=DØär]Äo%¤x ìQ[¢Cyýaº¶¢I°îšò8%l"qPOðþíxvÙkâùL¡T„WRøE{˲tÓ†EIA¡¢Ét2Àßamñ8æ¶êLE¡FíA&îÆyxK·øù³‘B'óÎäDJ¦€ÉÛê瀃ÈÞ’'—‡ÆH;ð,Ú=’ð¡ ³æÏGÎ¥e@¼ªM4¸Â·aËL5ž€þâúKúÚyX!iÁ‚ J\Àw²ÌÏ¡I”ª¡ŽÓVôíÊ,Þ aèVçóiuU¤¾¥'ˆÎHÌìñîr˜EUïÁ Ÿ’º’ˆ„¹'-ÄE¹¾„"艊ì“Ë­& ¹±Ý癹kNMxÐ~¹I¯í%í–H<â7cPÃoèw0×ip4ý^Ø\"¾×‚÷ kÇâ‰eÓü°>iÜ«¶âtöŒ…ê,VJ½š5“ösSÜRÖÃÓKéYŒï;~ SÐ|\nê,Y$ Qrô/YÛ¥² SðÕtAÞ )KôÞ‰ÄC[1M—„b äá”)X¯ïH¨³Ã7gÒ<›"Òž(o ÓÖ†­=`h¸~4§¹9¼®à‡¶ª<ùƒJð£æj0y®X8’B®YS¯G+hÒ/ú¯Jbö¥} ÔŸÚnÝ/@LsHäY;½Ÿ%ý‘ÜP!.½=_«‰FËRbòŽbÎ64 ]ÏD‡B½‰¶Ê ×ÜJbp§møB{S›8N­†(VzgmTÑÈMç— Ê{ ¡Œ„š(¼€¤€á¦Žà®ÑiÇçg8¦íQóf8‰ÄêZA‡ˆ ŸƒbìÏ!‡¥.èiÖ jм¤:í¼ÎFðòFJ~hŒDJÕ– íçL¶óá’þeœ®oš`©¥ääÑ[ç iƒÒ/÷_‚1î¹ó0 µ¶åùµGLц‡^¸½sð¤j–¶5†C"Nºýâ .;v2 ²@Út<4¨3¯(„£A–Ž  ‹Þ 0°Q˜Ø½¸Šš€e$KÂì ­ƒ{²o PÝ–ׯ˜ v }àcµk¨óyšNÅ­“w®kƒ­GónC×Ü…Òœ Î:\çå9húƒJ-)È vôµ68ö­s–¶‘:~ ÿa ¸J‡öêð€ØE¯¤tèÙ‰-ÈçL·uy`µ{Èÿîc±Xü˜7aà ‹ü `Ú¹Q´ í¼²ÜFv~ÌŽÛÞø ŠJh: Ø4åFâÝ2ä0£{CŠÚïC½& y‡2äö£ðä:…tfC[ ’æ_ºýchôI°(1K^kdJÒDèÛɶðnPµ¡C¨ª?Z~¨£Ýó+tzh¡€s££WÍ.]Ç^ù.«­Ao˜¶å` +8Ÿ•ë(h2ˆq*‘òõð–ÃVhšóÃDCSDÄ…Lh¼]$ ¼¤ªv4ÔðÚËC¥˜{îC't4]Bhá©rUÝ,š™š[±ÃRaƒfòòÏ>jéEÑ4¬“"ô”•¢FCÐáÔX4ûSž:ª¡±ÔÇ„ÉüíˆiiÄô ,Îÿr´#hû¦÷¿ä¾¹zßÕ8RÊI%M§ý’ÎìAl1nù7z_c„påÄÅuDfTP6QIuJ¼N V‚ö‰j¹'£6ˆ>¨sÐeÏ ¨iK.é°Ë²(Åê°YOqRp¸üúáÓšæ"¦À)kk-?™,TÃozથ£µ'k6Ÿ§dž¹Ï3õQÔž©ÏÇ<è÷ãWÈr´íîÙtÉ ù …•)ZýÜØÞ»ü„¥¦ÿ)àÕ|ˆ’„b›Ä'ixÙ‘ÈË®àv˜Ýy/ˆÆ´Q% G<ó·¾´‡†ºÊŒ°˜ð Ûk….‘Ò„¡`%\·†‹HTôqÒ±‚ ­ùXøð¢,Vx<ïйD=“–7Ô%ù·\ÕM‡û})yYa¿ëcÓ.¨=Í—'YV|:]HëMT-- Ùÿt}«§µ­à&=\hæG ïÿ8Ãòxl¿uLæžÂ›ïê ææ¹OÚB³3Yi·ü¬Hï5t¦Çé\ k8K ÷ˆi!ŠS§óÕ¿®A†4í <§®J^‡¦ÎЍ‰O½m¹}¦ê„¬»ç|výø|Vk\Š7n ]îöÙ©ÊGCCG‚þÏ‘"™¤ñ†ïkžX¸ì7îïìá]’fߥ%mr‚†à¾QôNÀñêüÁ¬Ú&Jf/h¨­‡ëùÁùP4ðW‘ÿðñ©cg¤u_»Øºˆ»ÇnyXF“‚Œ€O£ßáÃNúÿÐòš³…‚o8 [ÊQg¯òÞq.`é<°Ô3ö)ê¶ùŒ2²rƒ™$0<ïí¦ÏQ Š$èÀ¥¿}ç°BĉQ£±„¦„¢Ü~øüŸÞ¦A¶zèÐÜÂ2œCqë4"˜Í°D|`|·®ÖÛW­?q´_óvVy+N‰Ó¦ßsˆyŸ®sþrÅ7/ÈÄ~wÃ""Iþï?Z¡ù½Äg…´S­wÓ®”¿~úׇWMîâÍùXû³rÖ‡îÚzNTCÉü}d‚­vÚ<ÊtzrlI‹H¶Ü±bÒU÷f½. f£¡Ý=–˜t~âÇ D££›°I3ÿ 4Á]¨D->MbKGDDWh~ª”© pHYs.#.#x¥?vtIMEâ 5&³´¹¡ IDATxÚìÝwœÕýÿñ×”Ûï.[`—^•*M‘"öÄ|ÕhÔ˜Ä ÖŸ-ÆÔØc‹&vA°kb‹, MAA@¥Hï,ÛË-3óûãî.ŶîÂûùpEîœ{fæs¹ŸùÌ9Çè=èhiòL…@DDDDDDDDDDDDDdï °ˆˆˆˆˆˆˆˆˆˆˆˆˆÈ^B`‘½„­ˆˆˆˆˆˆˆˆˆˆˆˆˆˆlÍëñu£Q· À"""""""""""""ÒdUjwUaÖó<:ulG‡¶­1Œ-ú4 œd’%ËW²båš­·5"*‹ˆˆˆˆˆˆˆˆˆˆˆˆ4Ažç¥ŠŸžW‡w†Ñ âejŸ›¥z¤¬QùÏ¿îªs­Ú×ýzxxžÇQÇPPPȬ9óªÎ°A1u]‡v­[âºÎÏcktjß–eËWbšV£,Û ¾¨*OÚ0Rÿªo@«>4«y':¹?±xxÛ鯶mëÒ§ˆˆˆˆˆˆìÆ›U7¶Lœ+“ö],ÿüFÄÏsÖÚ´©mÿ;z_Õ˜ê[!ÊIEDDDDdå=žëÑóÀ®Üü×+hÓºUÞ?ôØ“°,_󱪂k8ä¨áƒ9¸_öëÒ‘¬¬L `ýÆ<-^”é3ùüËiÄâ‰çAUçÚ¦UÜw¿;ãLÓJm«,Ö–——“ž¡]›V¬X¹º2Q£^çvÔaƒq]×qð<&|†a˜•ûs9ö¨Ã1 ƒ#;Ó´˜ðÅ”F—ëÙ tE·+yå¡Ãèd&Øôòùœú\1ŽeÕ"YÛ$`&ITÄI`‚çkö;îzê Žt?â¡sâ?Å¡š? Ú¾QÆ4 OÙ=7&<¤ÝšÞ¿9sŽîJ×6YDây¬[6—¯ÞËcSËqÍ]“,{x¸^:톟ʹ¿îN÷N-Ér׳jî§¼>ö¿|°Ê \vÒf]× {Ì…\yboúä–°zÎ'¼ôøøtS³ò¦Àæ¶&Y§ü“—.̆·.åøo¨UÞ,"""""²“ä'™à¦kë^üH&☦]§°çy†ÇŸNû-úýɤE£?kÓ¾mÚ·mÑÇrYþ9<ÿÒküçíð ³^ÿVÕ$›g5ã»FÒ¢y‰x Ÿ?€çyLþè¿üë©1¼ûÞGôïÛ›ömZ±zõZÏ«{Þåçº8ŽS9ò×¥´¬Ó²ª ήëRV^N8Ä©ì¹.†iјÒ<»Þ•cÒip?ÚSNEÙÙYä´Èfõº õ+8».^eñ÷ÃO># qÍÿ»ˆcŽŽë¸¼ûÁÇ<ùì8âñ8¿:úp<7õ* ÄE½G;F/Žš†1ûŸ<–×v$ÃÛ½ÆÜ•~°Ìêv[M©ežG2Ãqâ±rÇaX¥“ù÷¹ðo Žâ x¸ž[=%XÕÐkÏó0K'U· †Âxf-úŒDñj˜‚ ªŸî®iZ²†No-"""""²ïÞ”p©h}Æúñ!~Ææ&Rk$Ùá 2‚àù¼ê|/•ƒUo®L%·˜¶yÛ®+åyåÓÆq×Í‹˜øÁ" ¤Õ•sžÇM}å·ƒþÅ;ÿKŸºã6ï}ààYàøÛÒgxÚý4‘ƈû†sÖÙi^ü ·Ÿy ãWg2ð–×{Úï8ÿØ×˜ø†ƒa©§Ý†8•þ(gâ:I\ÇÁð¡ìRDDDDDšm¥0­ §á†1M³†–•ùVe­Î¶}XVÝTÁ¹O¯lذ‘ën¼™¼M›8óOäÒ‹/¤en.žç±fí:¦N›Î-#GÓ¶mîºã6ö?'™À¬ålH©bs‚ɽYãöd2ÁW¿ÉõÍ”©Ó|è!œtÂqŒù ²²2ÉÌhÆêµëñ ¯µ½TmòêËGpò‰ÿWýê9gœÀ¿ž|ŽúdvÕ5Ê­ÖM®iß4¨6iÖçÀ<×%ÑuÇ´Š±ð³·yõÓÙĬ® =´9¦ëVi“v;œs3=õï½ö(cþ>‚Ó;ñëûÇ0º_Ï7„KߘȔg~C_ÃÀÑÜðñdæ¼s‡Z'>ü.“þwݯ¨r®m›.—½Îïÿ“›û æ†'U¶uùÍÏï´Ï!¾Tœh/Ž»òï<óü8>|鞸æ0ú§Åp=Ï 3ünôIÞúïxÞ{þ^y4ƒüño6ˆˆˆˆˆˆÈŽóG'šF¶é"=$mF$=“ß&i…0 “x«óxä7˜2º;Íœ$®cÒêÏ/ðé{c¸{` û7\÷Ö«¼:¢?¿¾ôïŒ}a<ﻃ»NÊ&Ãuªó5Ã0ðÏâ£IIF2ˆ¤e²×2iÚZÃ$è·ñ<_ÉŽÛ8®ƒëºŽü \}!7Þñ[†PA|¿ Ïp(ŸúïçE…f¾ù9K?ôÁu’•kP¹ø¹˜kÎåéG&°±z©aå•"""""² ò-/UH¬â… GÓ §5Ûê'M'Šòä£2kêDfM™ÈŒ/>ä‘ûnÇ«\ã¶– }å°£~Eï^½xóõWøý©¿£c‡ƒAB¡;uäŒ?žÎ»o½A·®]vä±ô8tsº–疈Ƕ»=¯àôSOâ_? ÀÁýû²iÓ&¢‘píÏk{ Ïó8bøPþpÆ9œö‡38þ¸cp]·Î3;UåÈ8å¾øàu¾øàíü¼ÎÙ<%5¹ž9¤Y÷+ \Ç¢ëaÓÎ[Ȥ)ë)øjß&lÚBg/‘*þz8väýÜwzOÚ•Îâ‹)‹)L·qò*˜ûö‹L\8Ë™<ö_üûÕoY‘HVŽàõª?Ø>YBÌhKïÞQH&HxÝ8¤¹æÞ™¨©›LÄ™÷ÎK;íÓu’$Œnœ|Ûh®?ô8´ú§ßÞ$“‰ZJ«ó¨HÁpW_y9¦i2iòÎ:÷|ÊÀAC9ëÜó™4y –eñ׿\E(’N0’†íó×aúg'™Üîöªm;uä»yóÈÉiAyy9x>Ÿ¯Nç~ôñt?è`æÌûžôfͰm;u õ¨z®Ësã^â¡þ{»mþõä3<ñô˜ê<·>ê<´‡GÒ:ˆ£‡¥a®úœIË‚ø™ÊÄ—rèGrxË·Y°Î&Öód.8È ùÕœqñ›,óüØ>?–í'¾ìU&wG¶XÎÔñOñRi&`¸rAåÔ ®ã°ú“øö¢?spßÞ4q*KÛ `X«k^ü˜å9¢j˜´ëòû¯2ñ×§î°Ïd2;ø\°ŒUÏ\ÁŸ^FÜh΢sßÀcú’e¹-hm:ýø)o¾ô!óòÀ „„#ø,ýDDDDDD¤. ÃÀ.þ˜»¯M#pë9üºSO†Ú“a§Œ xÞˆíÇŽ—SXn@0ˆë:8ŽŸÎÁÌW¹öÙÅÄ;øt!ˆˆˆˆˆÈ/$5@À0-,ËÞœ9Ij[%5 #µ†o „aô8ŒXE)¿:æ(®¾òÿѹs'ð<,\ÄãO>Ã…—\N !MÃçV£ë2ç´·ƒM^õ¯U}¦^«z½áƒ: Àöù1- ¿â}yàžÛxó­wê߯ibš&ÏŽ}x¬‚뮽z«í<ú/žóáh³:0Þ’Y·K$UlMvƯr áÏ÷ÜÍC÷\ȱY \»;‡ NÃKÄÉêÒ‘+ÉO“¿d­/pz¡h:PåÅa¾@ˆ@0ŒeÙl9!–ašøÖNä³ è~q²úDW–0ñ£E$+S®n[ùäÀŽút“I²Û¶$lØ´½à?Ìùn&?Ìùû†¸FÛJP1åMÞ\î‘uÌH^øðS>á.;<‡ˆa¢çµEDDDDDê’0©|- ñãËüå”ßpôy£yð¯XRæ#­ç™ÜxÅÚ8‰ÍɹçýìImÏó6OñœˆáøBÃaÖ®\CÜ3ðûío\x®G2÷xn¹~0íc³yîž·XbRkb;kcaš6>P8`8‚eÛÄ**ð0‰¤E±* Îø¢¤=¼òr\Ç!ÙñLn8ÉaÚcOðyYê†Á–Ǥ٥DDDDDdO©Z¶Àçó…ñùü?ÛV«Ï00M Û ‰Фóð÷ѯïAd4kFFFÀ³Oý»räoÛ¨{ñ·–,XH¯ž=X³v€X,¾Kú7- „íósóuWã÷ûyáÅ—yðŸcÙv ´FjÖ*_€P$Êó/¾Â=÷ý£zûÃ<Ê“Ï>O0Å篌[=k“u\9ýóæµe`äôæðœ­t6˜¶/}L±ª-Û>;UŒ †0LÏu1»úƒ¶m;õÄ€¹¹Àj¦ea³ž Ÿ|Ïý2 G3JéˆùÃ|¸8” le[Ã41°vØ'€ë¹”—•ãá°æk¸ü¥õ¸•Çd™åäñ9ŸpÛï¿ä¥¡Çð‡çWÇÇy·@ÎÕ#¸wA:fŒÙ·†mû EÒ°|16ÍŸÀS³Þå©ç/bükЯwz0‰«n:;ɸ ð‚ÌIÓJ•|+oZx[d€žë‘HÆ•wü™_e®æ‹Ñ×óÄ¢þèæ$ÚuÝ´1ñcúU#è{ÄYX½â,7̦ioñ†·ó> ßÿÀ§­:€èýS™í†*oHøðBøý6þx K¾x>~‘×–½Æÿ.oMÇö&îVþPˆˆˆˆˆˆì“¬\öÛ¯œ5 ò(¶}ضŸ ’%ä'ˆÇ‰y@›Žt°¿"/ ×á10Êu­ ³Æ¼Õs=’Y‡sõ]—qZëÕLºc×¾WŠMôR¹¤ëº;icà¹IB]ûsH›li½ØÏœÁ‚yÓ™\r'r2ÿ—3Š×Géöë¡`ÅY0ežç‘÷Í›¼üS<5Ò×0ð²úrÂQ]ð-ú„w¾ÍcÙrÏs1Ð:C"""""R[ŽÞu=Ïuq g«üiK®ëâ¹nÖ–…mF‡\:˜[n¸Ïó¸ýîûùjÖœü5 ˶éÙw奴ÌmÁ ãÆÐ¡}ûTŠiûè5`n2É3Oþ ÇqxåõÿÒ¶m;0 òòòSûÝEÏÛ†Éù]ë:„"éøƒa²ÀðVE`ÃàÅ×Þ ’–?lpñêP®šþ9Ñ}Ç6w)ÿp*óüDýAñ|9£”‹OíÃáCÒø×[¯óƪœ}е<ú`o>úÁ¢y“ï~ç—$X¸xÎÀ>œvã•„fÎfâës˜³E°ªFû ¿àݯ/â°_ŸÈpo>ã>] ¾,Ìmf¯6¼M;ïÓ4±¾{§fÁ­FðèÓmycr´?ˆ%c¸æÉ%t¯]e³`ê|”4§ÿqmñ%3w^1žd«GÊEDDDDDd§7$*öû£JÛÂå,^¶‘_öïÖ’L»˜yãß`†canœÅ´egqp—ó¸ëјU²ýúE±jzºw'û‹gɵ÷]Ê)m<ܵk¨èw ·œš)ÊÊÿŒÛŸ™K"óh®½ï²í·yvžçQø¿Û¹¢¼'­Èç ;9‘'Ÿù-‡_y—>ú–fr`¿,¼%ÏñÏw ñù¢Ló0“c©›/®C²Çuzd'Òf<Ë].ÃmFe*-"""""R¯<Ëu’Ìœ2¡úµYS&ÖØvðQ'àU|¿žüñÏú©¯ª"°iÙüí†kÉÉiÀßn¸–“ÿx^ƒ¦}6 Ÿ?@¤Y&P„¢Ò ÎþóžöI:thÏÀçˆÇ¸ÿÁG¨¨ˆ“••E"‘`CÞ& c× è4 “1Oÿ ¸äê±, ×qا±ÕÓ@åRD»fºìÚŸ½®ëÐsø@rÍræLŸ‹I#M'M' 0wúwã§ûð!ØSøÇˆëxè£E´>’“Ž?˜ý.ø“f‚ùÏŽæVèv<'ÿf(}2Ëj8q ÛÎçógSfÙX Þ烡ÔÐò-N>5ÜÜaþ³·m·O°,~{¯_}WŽ™Æ’èPN=ëDŽêcC¹Mš›$û‰ÙkšÓý¨Ó¸àCè\<…WG]Ç£ B©‹EÅ_‘Z¦‘©‰Íuûü§|µÖ"³sOúv°(úqãn=Ÿÿ÷R®/€ÏøžçG>Ê›ó‹ñuèM7s*ÿ¾û;¤f’Ú&¬Nô¶ü<×%Þ¢‡´LÍeµìϱ'üÇ÷+Ž;ö(~uHk‰øNÛ8NÃ0ð»k˜ù¿wxw¡¾¾€ÉÊ—¯æœ{>à«‚6ôì’`Ù'råE3=Å  G‰6Ë"š‘E$=05µWÕTh¶?5Å´¦‘zåZžG2™¨UÛd"ŽLŸñõV¯OŸñ5Æ6¹V}¸®“šýhóÁ¥^k€TÝÏG !œÖŒHz&E¥œ{ÁÅ,[¾bó¬¾Á}>Œ_yƒƒêa,Yº×õRçÖÀœË«>%·ú÷UEso|Ž›k¡~lÛiíºµ’ÞƒŽöj1Å©(+!‹aûý„©śԶÒâ±r,Û‡?ÄI&‰ÇÊqœ$¦abûC,Û&« V^Šë:øüA¡0žëRQV „"iøüA'IEi1±ò2LÛ&IÇ’ŒÇ(/-Nµ §aù|©>ËJ¶ßg8-µïxŒxy)N2 F* ÷UVØ]×!^^F2Çu]LÓÄçV.¸ÄÔÐ""""""µL–Sàªü/«À©¼PkUD=ס¢¼4•':Njm* ÓÄ_¹¶T<^a©õ„-›XE9ñX9¶í'‰bûüx®K¬¢Œ²âBâ±rð¼Ê$Ú¬|º:ˆ?ÂuÊK‹·Û&I«ÌwS7/ªŽÅó<â±òÔùÄãxž‹eûÂa,Ÿ¯ò&CjMb7™$VQF¼¢ Ëö GS`C¹¥ˆˆˆˆˆÔëºÄ+Ê(-. ¢¼Ïu+§<Þ\<4+s©p4 ƒòÊZ›ë:©5gCaÂÑfø¡z×¾\×%VQÊA=º1ê–Áóyû]Ìž¿@0²KjjÕËü$ãÄËËp]—@(ŒÏÀu]œd×u8öÈáäòÍœy˜–ÊñêXö<'™àˆaƒð z4îÆ¼<¾þz&ô§yvvj¶ë`ŸNš†eûvYñvW¨}×qp’ÉÔÅaYض¯z¡h¯2È©§¤Sss{©À;I¨žÜiZ¸®S¹-Õ—eÛàQ½H´mû0-ÏsI&8ÉDêý¶òð§ú醪×RÇ·ã>«Þë8Iœd³òxMÓ­üp«žX0LËöUŸ«žÐ©[]•ÿ9N×u1 :ײ¬T.æyŽ“ÀI¤ò1Ã0«o`T%îU£r«ò3'™L=p\Ù—iYÕ {2Çu’[=•mn±OÏsI&ÛmS•»n™[œOÇITæ¶/•Ãn[Øu]7uÞ•Ç^uœÊ-EDDDD¤!y–ã$HÄc©ÁŽ5MålX¶í `L¤jmžçUç&>Ÿ¿AëÍzžK"§¢´˜Š²‚áhåµþ]: ³ë¹¸É$žç¦r/ÓÚ*çt+kVUñ·ûÞªìy©Ù¨¶Ø– kê/õ"†a4ípêd¼Í'Xyñl™´zžWùŒóSryÞæëΠzÈuu_Þæ×· `U»ê>jz?õísë¹Í«ƒö¨Þ¾í6%è""""""õ¸)P•ƒí$×Ú*§«N<·Þ¾UnW™ƒn›Ÿz^U¾XóMÃ0¶È ·ßf{9à¶çc?Ï·½‰°í±‹ˆˆˆˆˆ4(ÏÚQÞ³9¹©Î»R¹–Wë¼§¶¹^ÕÑ-mZ»iPeuNYCYµÎ±iš;ÌÏvS×uH&â›×ø­Ú×–uÏ-r[ÓÚ<øµ1€íº46Øñ\à5^(†AMo1¶¸è¶ícÛv5õQÓ±Ô­Oj>cóvi¸êl'¹Öörº­úÙYZõúÎïíå„»ð|vvœ""""""õγê¸~ïŽr­†äz†iaøL,Ÿë¼n7æ–5呞eÔ˜7Öq¦™Z^Èr·z}óŽ·y°Ù4S³%7²”ÏÖ©ÆðÐë®ØÿVÅdËÚ£ûÞÕTaïXÖÔÇ("""""""""""""²wPXDDDDDDDDDDDDDd/±y hà M'IÃöù1 Õ†EDDDDDDDDDDDDD#ÏsI&â”—SVRžT€MË"+§5¶/ H‰ˆˆˆˆˆˆˆˆˆˆˆˆˆ4r†aâóñùƒ„"éäoXë8˜†Š¿""""""""""""""M”Ï ³Ek0 ìp4]Å_i4<×aÅÂoh×µ/†i)(;áóGÓ1C‘4ECDDDDDDDDDDDDD×I°ô‡àÄ𜠖þ0×I(0µ §aÛ>¿"!""""xžKþºåæ­&V^‚ë8 ŠˆˆH#dZP”fÙmÈÌm‡a˜ ŠˆˆˆÈ^"™ˆ±üûø|&YYæðÓ¼©t<à`lPAÚÛçÇ8ö÷{ …ˆˆˆˆìóÉE¼‚å?~“Œ G°lÃ0‘FÈó<’ɱò2L+@ûýûëF ˆˆˆÈ^ +gé÷Ó …‚4Ëh¶Õ¶âÂ"JJËèxà!øƒakôx¤ˆˆˆˆìó<Ïeù_ciÍ2°}>EDD1Ã0ðùüDÓ30 —å?ÎÄó\FDDD¤ ‹•³dÞÒ¢áŸÒš¥“žeÉüiT”)`; °ˆˆˆˆìóò×-ÇIÆ †# †ˆˆH Gp“1òׯP0DDDDš¨²âM,?f͈¤¥m·]$%3³˾ŸFiáFn;T‘}^áÆÕ„Tüi²á0…W+""""MPqþ:Vü8“ìæÙ„#;¿? ‡ÉnÑ‚• ¿¡(oX€EDDDdŸ«(Ų-BDD¤‰²m±òBDDD¤‰)ܸŠ5K¾£ynP¨Öï ƒ´ÈÍaí²y𠦦ïÇ ˆˆˆˆìë\'‰aèÙH‘¦Ê0 \'©@ˆˆˆˆ4!yk–·f1-rs±íº—,}~?9¹¹lXù#Éx-ÚvUP+©,"""""""""""""{̺å?P”·šœÜ\,»þåJ˶ÉiÙ’ ëWâ8 rÛˆaû||5ÌADDDDDDDDDDDDDv;ÏóX½x%›Ö“›Ó âoÓ²h‘›CiÁzVÿ4 IDATVŒ§¢H£GhÑ2w—ŽÔ5M“¹9lÚ°‘ù3>hò12 8œ6]IËÌ©óûU‘½NqþzÖ,™CVVY ˆÈ^Î0 ²sZM‹½â|båå¬Y2è]ç"° À"""""upá¹¢G÷nµj›H$3þ5æ}ÿ£'"""""²‡­_¹€Ì¬,¡‚!"MN "++‹ «Ö¹¬5€EDDDDê ¶Å_ŸÏfÿ®4‘_@¼¼„ Š¿"Ò„B!beÅu~Ÿ À"""""»Y$¦Û~ÉÍi®`ˆˆˆˆˆˆì!ZóWDöÕÿ—©,""""²›Ù¶Ezz”vmZ+""""""""²[©,""""²§ ï³ûñûý‡÷Æ_È iNƒûsÂ'põ‹·p~›xÞ×ìÔÇyÿÖ¾4ßæœ<³ ¿ºïU9¶öO§÷W¸gH™®‘]@`‘&$ÙípŽu§3£h(CÚ%5e‘=ê¡{GqÅ%çFª_‹„Ã\~ѹña^îñ$'Þõ]½Û³ZÐí¤ó9ÿˆntI/fÃü‰¼òì›|¾¾æêõÑw>¡³§RÚo8ÚXÄVLåϼͬÎâÜcº³vœ‚9oóÌÿcF¡]›ö øãyœ5¤#mí ,Ÿö OŒ›Á‚r°ˆö=“8”~íBøò—³lÊî|q1ž®+‘=íñ§ÇsÙEçÒºU—^xàѺU.›ò xâ™ ‘Ò`‘&ÂíqG™“˜²À‡ùýL¦CÚÛÝæ~;úô,áÛ§nà’ËFs÷üžüt€(Ï»“ÛúÎçý{®àüëžâõ²¶´6SUйSçRÒ{ ý}•}¹ôÅÂisj<Ïò·üñco•eDèxÎÝÜuð|tßU\pýXþëĵ;ž¾~w;YJ;zXȬǯçâÿw­:”3ï¾kZ|Á+w^Íù׿ÀÄçqý]*3nç߯5Íßå©ëFpæu/1¡õ•ŒúSœìßréÕ=¾w׎¸„‹îy…צ­£HÅ_‘_DAa=1† 7ѺU­[å’_PÈcOŒeS~$""""Ò@*‹ˆˆˆˆÔA~AaÛáóùvÁžÃtrs¾bVÜÄ*ÿŠ?dÑgPw¢[µK°jÒÛ|¶¸¢‚¥Ì~å#¾ˆö£o›nôpN¶IO½È'KŠ(\÷=“ǽÏÔdj$®1g2Ÿ›CÔ5@2c0CÚ}ɤYNGïõžxñy^ßâçñ£¸¤S¬º“v'¾žÏž|™‰ËŠ(\3‹{w£§òûÎvÎ5ÁªÉïðù’BŠò1íµÏ™_Âôw§1o] %«¦ðÒ»?aØ=µìãøÃ¹¼7vó âÄ6ÎâÍ×¾¡üàCSÛ3[ÓÞ^ÏŠ—±¶¤Œ‚åsùjq ®.g‘_LÒqH$’›ŸL’H&‘]@S@‹ˆˆˆˆÔÁƒÿ|ŠV-s9þ¸#i×65ÂtÂg_²`áO?k›H&Y¶|%ûwëÒàýº¡AÞ¿‚Æ,à RÆóVâœ<ˆCÃ?ðq™UãûŒŠ b~2|l݉nÆ<Þ^Zs`–Oç‹Ù#Ù¿ öüM}0pÁ‹þŒO>DëV-1Œ­×´5M“žÝ÷Çç«\§ÖmÀ˜Ó>Ã8,=DÆ•/ñú–¯{CÖóY>žaí¼ÓÄ2mls{s—2oÊ\¼?õ£Û‹Ó°´eÉ´Ùlòê?qgZX¦oëi¡½$‰¤‡ã4d îæs0qâÉÙL¸ñ>^ÎÿyŠc$òémòMÃùÕC8æ¯'ñÇïîã¯÷Ãr×ÐE-"²‡8ïŒêâïcOŒ¨._tþ™Ü÷Ðã ’ˆˆˆˆHh h‘zúàãÏøàãÏÈÌÈøYñ7‘•Ïg'ذ1Ÿ–,¯ßŽŒtú éIÚ¤›øÃïNçøªŸSþÂýßçpРý‰Ô¢kí –½èÓmû…Wã»/™˜6˜Aû2¤Û4&ÏtiÈR¹öÚ,1{Ñ»ëæéž]ÿØ!¥K6í’ÏÁ^µˆï“½8¸o` Nó>à•Goá’›'òSßã9º¥¦ù%´mÓŠ•«ÖTö-*.á±'ƲjõZÚ´n©‰ˆˆˆˆ4 À"""""õ0á³/ùàãÏØ°1eËWnõóÂÅWNaY^QÁ²å«((,ª×¾ÜÈﳯ¿øž­&ÅôV3mê ܾƒ88¸óÑ´VáÇügr ¹ð|~Û-´Œô9épú[›K¼fÅt¾˜Ý~gÁ°Ÿ&ñEqÃ& ²ò?äµÉ9 ¾ðÏ¿_”´Œ. <÷N.y…7æøˆ'LÒ³3ðõk–~Î[Ÿxt9ãJÎîNØ4±›÷ _ç NúA ~³Ø,ÚtÉ%+¾Žõ%J‡DD~ W]7Šû~b«©ž‹ŠK¸ï¡Ç¹êºQ ˆˆˆˆH鎇ˆˆˆˆH=,Z¼t‡Û]×eáâ¥ÕEà†°ú ã°òOøø»m‹±.%Ó§234Œa=j1šÕ+æÇgGrÏÜ8ö†Gyn|Áäµ¾-•1Ê|¢=;°fÚ·ä{ êVzx ÑHD‘zSXDDDDDDDDDDDdkѼ9ÿ~ä!"á0wŒ¼…®ûíǪիùÛèÛñ›ýúqÒ 'pò‰'2 ?|>Ÿ#Ž­ˆˆˆˆˆˆˆˆˆˆˆˆì9ͳ³¹ó¶Ñ´nÕ€¿E×ýº°jõjn¸e$›6mR™aC†0â¼sÉÊÊÚêõü‚ž~n Ÿ}1IA’FC`‘=$£Y3îukuñ¨,þ®Qñ·‘:á7ÇqÑç׸.sfF½ú*232ùïÛo+XÒ(¨,""""ÒÈtÿ'­Ìû‰Í]† ¸Ö!r*­6ÞGù‚õx€k ¥øÐ´ýöV´r·¶/. üì=3—²Ž¿Çײ-–•Ê›u!÷»P\æ§ ÿ-´È›IqóC°"~|e³ .|Š'õ~#›²§a·ì€í+ÁW8ß‚ Ä*RÉuAÿ[h¾éJ²Æ ›øKgXø)›šŒÕº V N ÿì?'O¥9®Ù†òΧàËiƒmæáßð.Æ¢¹$œšWÂÙù1Œ¢åê·Ù”{<¾ô$™so¥dSp§ç."""""ðʸ±øý~üþ­s…ÓÏ:‡X¬‚D"¹[Á5l’(O©‹ŽÚqáy®±ø»¥?Ÿ}&3gÍdñ⟴½”gضi6þvUiì_Ú7,¤dÿI3—PæšT¤õ h%)ËÚŸ ±Ž Ï <½;鉯H”úw{ûŸ PÔùj23'bÏyŽ §%…íÏ&Z½E’Ô /³„´ï#Ë ¸ã…ÐýhBÓߣÜI£h¿¿’Ùìcì¹ã(w;RØålšõIüê3*\ÏhŸ‘öã½ÄcÙv¹ˆpÿÞd®|•䜗‰’ßãlr;/†ÖQŠº]IŽ5÷ëÄÌýÙtày´ìr‰y5œC¸ÇKù~ÈX4Žø¢RÊÊýµ:w€h4ºÝm%%%»}ÿíN”˜Y˜†©£.=ñ7X–µÓv–eqÂo~Ã-#G)h{1ÇqˆD£dl3xc£°ˆˆˆˆH#Êÿ†cioBa˜DFG²W}ÀºÖ}H O ¢4D"« ‘¼—)÷ŒÝÞþgÉoF«<"³ß¥¸ÄÉ"B‹¿ ¤eW6§CI‚ë&PRìà„ðO“)lÕ›´ð[8‡b´ZOä›÷(. `1ðïÈñ´iþ1¬T¾bõûƒK¿¢"'‹Ì³‰Uø°˜ +Ž¢¢c`-‰àØ9ߘ1›â¸‹yØK~ ¨ûAØ &Ôpµ;†´%OP²&³–ç."""""²cU£ƒ·õÂŒ¼ýï î¿ÜH§ÌnA EW €ëdPŸýjßöƒ¶F°ì]<×%??ŸX(D j´Ç©?å"""""œ•ü/¿ nfÙx%Xë§ávÃËðÑ2˰6¦¦kÞÝí·µ#`,À+©ýó¥¦[ŒçøÁ„X¸=o^©oó9'¾Ã-n†ɨ9&‰bü`n>"3QŠk¤úˆ[b[ûSØïvœÁ·â ¾®XfÍOm×îw‘mE£ÑZ4o¾KúaÌPºŠ¿õ™¨uÛŒŒŒN-M›ašøâñx£>NÝ¥iôJ±6,%Ö¦+æªæøý_á”Æ°òV“Èî‚›×€o NQ`µßšg˜`X`xu8'oËì ¶Ê‘ðÀðÜzõiºqpçÓìëg(Žûj“ÁÕëêwî""""""[;ý¬sj|½¬¬l—ôï÷ÊpË‹ð¢¹*×Q~qŒVYáZµ-((Àó”îÍ\Ï#‹ÙÁ”î À"""""žGxÓ·äwíMZv˜Ì·¨ð ´i>ùzÈîJfþ ”»Æj¿Í„ò5”Ñ£™›¬:Ÿ¯|-FoHOB~ja×Ü#² cU!¬ûͲe”xÇâeû`Íî;††ž»ˆˆˆˆˆìþ5€C^‘Ä:Š×9˜¦ ªQÖÚ´Ù9ùˆÞµk;}›6mRÐöRt"‘ÁF<ý3¨,""""Ò$X‰™8E·ât\‹oñ b°cßR»ˆ@» ìE˨آH¹»Ûï-YÞë$,ÅŽOÆ]w,%ÝN#0ÿ**2¨h{0!£vI¯/ö9¥ëŽ¢¬Û)ø¿Ÿ@¬¢eN"+ù6ñü@ý╜«Mq—s Å^%–_A"пonqügçPßch蹋ˆˆˆˆÈ¾£j”oËÜFßò72š5«Þö¿ÿ¾Á’¥Ë¸yä( ‹ŠvËþ³ed:+I}uð¿· 9ñ°XÖŽúu‡wÞ{ì]4m·4>ža`Ûvê!ŠFN`‘&¡sÃ:¼nK¡ õ5ÞðVAž…Ùv6^o¶wCÍÁ­zÚµ„ô…S²ßiøzÅöÖ¾a5E^mGî–¾ð!Š»œ‚¯×Pl#fyïb};ƒ˜[ߤªœôÅ÷P?D×›0ƒ&VlåÏS^Yþù9Ôçzî"""""²¯¨廨¤„ënúwß>š¬¬,–¯XA§Ž¸cÔHn9Š¢ââÝr &~¯LF¬Y¶€'Ÿ}Ž‹/8‡ëû>;vËW®ÂP]~yƱ¿¿XýEDDDdŸ6úûdµÈU v¡DðDb‡dÒrÚÓ”Ä|:wÙí6mXG÷CŽS DDd«\¯]ÇŽöøÚ´nÍÝ·æ¬ó/$#£5’Ž:ìö‘ÀR?CfÄù&»²h_%??Ÿ'Ÿ}Ž/&© Én±béÒ:ÏÕ`Ùåâ‘¶„cóHÆm»ˆˆˆˆˆH V­^Íù—\@AA!7Í]·¦SÇÜ|Ãu\wÓߤFdò”)Lÿj½zô¤}»v¬\µŠÙß}G"‘P€¤QÑ ‘F.yäCMK7JŽ8yŸû¼¶ÛfÝú |3{6¥¥¥úÐE~!*‹ˆˆˆˆlGvv“>D2™Ä²,@`‘=­ªì8ßÏûža‡UXDDš$Ã0ˆ„Ãthßž[oº‘k®»€?œ}îVín½éF:´oϲåË}º·n\yÙ%thß~«×+b1^}ý ^{ã?¸š¹CdSXDDDDd;ü>Çq0MÓ4«vÙsª À®ë’L:ø|>EDDš¤x<ÎM#GqëM7òégŸÓ¦ukÎ=ëLúöé À¬og3vüx>ýìsn»ó®­¦ŠnlêÝ‹Qû¾–L œ}ÆŸhݪþóQ}ø"{˜ À""""";`–eaFuñWE`‘=Çó<<ÏÃ0 ÇQ@DD¤I+((äšën MëÖj©à‰ˆˆˆˆˆì£ÆŽO¯ž=Ø¿cŸ~²úõââbÆŒ¿Ë÷òŠˆ$ÖQ¼ÎÁ4MðÖßÔYQ†÷ª]^»ø§ŸXüÓ}èÒä@ÒqˆD"ñè_PXDDDD¤ÞÖ¬YÄò&]6 šdeúÈH7±,(.ñظ)Aq©‹[5ÅWy‚uëV™« ŠˆˆˆˆˆìC22šqëM7òégŸsùU×páyçÒ¿rÍßofÏf̸ñôïÛ—k¯ÎmwÞEÁ.œ69ÛYF¦³’¤hp_S?^Bþo&3#}§m_ÿÏÉnÞ\¾4yža`Ûvê!ŠFN`‘z2Ük˳9éW¿eüøñ”••Wo LÒ"&7%9ꨣˆ\&Nü´z{8à*€""""""û¿ßÏ£GÑ¡}{Þyï}î¾ÿŸµ»öêáìßµ+wŽÅU½žx<¾ËŽÁÄÁï•5¸Ÿdi÷=ø£ÿv3>ßÿgï¾Û(>ŽO²åíx$±cg'ÎpÙ‹$Œ2 ´@€(›2Ê(P6…2Ê.Pà-³ -›°JÂÌ Ù{/;Ë#¶ã=$Y÷¼x*±;±›ü>-±žÓIwzî,=¾Ÿžç ¬w½ïœÅÜù  Ò Ò‚4ñ˜ˆˆˆˆÈašØ?€ØØXæÍ›ïþÜ÷Çxþ÷fo"##™={Žßý£z@pK•("""""rŒ0ÆP\RÂŽ;yø±Ç«—¿ÿÎ4Þ{gZuùáÇgÇΗ”`Œiµ¯gÕê5Üuß_Hݱã€ûÊÊʘöî¿ùÇKÿ§/r¨°ˆˆˆˆÈa˜`óÅgXnY0qL8=º»èÒÉÅôéÓX§wGCa®z‹ˆˆˆˆˆ+¼^/÷>ðW,ËòëÕá·^^^>ºã.Œ1x½ÞVýš6mÙÂM·ÞNŸ>}è“Ô›§“̽Y¬\µŠâ’t‘£D°ˆˆˆˆÈaÞ/¡CY±¶æZË‚k¦¶gPß¿'«ïØI™»&ì uò»Éá¬ø¯êPDDDDDäXRW [XXÈþý|›rØçæfÃÆM›Ø¸i“°H+¡XDDDDä0åXLÿgÞÿ*”]nÚE8;,œ £Â1¾Š?ß'dîû}˜9'Ÿ=™^¢Ú99ïô(Â2³)-׌,""""""Ǻß]z¹*ADš”`‘ôv·‹3KJ¹êì(Œóï,åÕŠIèèÀ逜|ö>þyO}ºU¾¾ŸccÕ¡ˆˆˆˆˆˆˆˆ4-u99LóR#Èݼ;·;·“WÎWbøye9³—Sçã®óèiW¯çÙ±¯×†©EDDDDDDD¤É©°ˆˆˆˆÈa*ô8ykV7æT1ço÷xf ¸ËÁçƒ °(ÇΪxŒmûøiU K÷tRŠˆˆˆˆˆˆˆH“S,""""r¾ÜM|Ä^Îá#ÐUÓ«×`¨éÙ.÷°2¥§çÄc4ü³ˆˆˆˆˆˆˆˆ4À"""""GÀx}qG6epŨbƒq„`9Õ+øÊÝ—ðÉ*¬JÀk[Õ7n¼*QDDDDDDDDšŒ`‘#d€9)‘ÌÛÁ øÆæÃé„Üb›s‚Yº'ŽbS•%""""""""ÍJ°ˆˆˆˆHñÙ+ÓÂX™¦Ê‘£Â¡*ùeP,"""""""""""""ò ¡XDDDD¤o9.Œ1Õÿ‰ˆˆH˪ý9ìr¹ðx½ª‘ƒÐÀ"""""õÈÉÉ£_r›7n£¼\›EDDކÚáoRßÞäää«RDDDDDB°ˆˆˆˆH=v§eÒ9!Ž‘cGà TÓYDDähòxËÉÉÉcwZ¦*CDDDDä tKDDDDŽygÆØX–ÿ )ÆvíÉ`מ U’ˆˆH+fŒÁáÔe.ÐÀ"""""‡Q^^®Ši£Ê˽…„«"DDDDDP,""""B»ö‰¸KKT"""m”»¤„víT"""""(!:® g¥%Ū ‘6¦¬¤G@Ñ»ª2DDDDDP,""""‚e™ÙH’ IDAT9èÚw8Æ8(ÊÏÃëõ`Œ­Ši¥Œ±ñz=åça›ŠÏq˲T1"""""@€ª@DDDD\Áô8ŽÜ½;ÉÏN£¸0Û§yEDDZ#‡3€àpbzݱ«Â_‘ZŽJlŒQÍ‹ˆˆˆH«ݱë !© Ð""""""""r45K¬€WDDDDŽU‡j + ‘æÔdð‘…¾ ŒEDDD¤­j\ [»Ý¬0XDDDDDDDDšÚÀ }M£‹ˆˆˆˆ´-µ¶õæ¹Ö!ÛÓ ƒEDDDDDDD¤)V|èà×Ôy³Ž"¹WDDDD¤õ°êmÎpeþ¸ZmlÁ""""""ÒÖy½^UÂ1 00P•ÐJ5*>xðkü~ìw“†'Á EDDD¤µ³ên·Zû·hkXµÛ»Ö~÷×ÑæV,""""""m•‚A‘£«Ápýᯩëu'ÁuÞ[çÓ‰ˆˆˆˆ´Z–iÀâÊØ/¶ü׫¹Qgû[!°ˆˆˆˆˆˆˆˆ4VƒàºÃߺ‚ßýSàZKM¥ž¼×(‘VʲhÄZ~mÛZ%«v(\W­w¨ X!°ˆˆˆˆˆˆˆˆ4Ö!àzÃßzƒßýC_¿5êwM=ÛÕÁ‘ÖŲên¨¿àÖT®X‘ëV‡ÂVåZ¦¦\w¬XDDDDDDDDßA`S_XköëÃ[ôÖúV?Gå²®—Õ;i°ˆˆˆˆH«â×–=H&kU·‡­êuƒý‚àZCC×ÑX!°ˆˆˆˆˆˆˆˆ4T½p½sþšZñnÁïþ¡¯1<°j­êÅ57•‹ˆˆˆHÛ`«v¡æ&VE¸öݘ:ÂàÚA0•CC[uu®nŸ+‘C©3>ذÏõ…¿SüšÚ½~M­`×ï¶ÙÿÙëÚ!iö _MíŸMÍ:Ëk涪ûW®BíQ -ŒUÙcØÔô®é ¬á EDDDDDDD¤q¶Z=áï½~k-Û? 6u ]ë@ˆˆˆˆH+WçÜ¿T¹UA­©šÿ·²ëW„·~«Ôê lY EDDDDDDDD怸ÎyþÖüÖê\;ð5 mØ”Qɰˆˆˆˆ´ÖþA¬å×\®XÃŒU™ýZ5põOüBÝêxØc«°ˆˆˆˆˆˆˆˆÌ!z×þÖ ù\惩€k‚ߪ¾UAqÍSÕô ®™&X¯ˆˆˆˆ´N´T­ª—»ôRèV ø\+-¦æqU=«ç 6ê ,""""""""GÆ/6 `÷ëù[;ü5Æöïõk >_9ž²Rʽl_9¦* ®Øv®cH=Â""""ÒJÔÝëÖò»YWô¶, ‡3€€@®àœÎ€š9€«ÛÀŽÊзâ9ü{×Ó4W/`©ÇAzï×û·:¾õö¹:ü5vÅZ¶MYII=»òè_î ±SN§S¨DDDDä˜SñÅH{Ò3¹ïá§Øš²‹àÐp,‡ƒŠþ¿ 6U!pÍHÑVuë[½€EDDDDDDD¤á½Š©=úsýáoeÏß’Â<þöÀüê„qø|>|>^¯W5-""""Ç$‡ÃAB|GÞyõ9¾õ<úwB#¢*{ Ûu‡ÀX5£I+ø‘F¨€ý‡[®Ýû·v¹j>³Êö K‹ ùÛwròı¸ÝnÕ®ˆˆˆˆólÛÆ¶m|>§ž8€{ž°ˆª5°¨è\1'på­š˜š¸& Ö0Ð""""""""RÇ!ר= teïߊKRUÿ«(ùʽôKêÎIÆàñxT³"""""µ›ÕÆàñx8å„ãIêÙ_¹×¯=]u«v»»j‘†ª7®=÷oíÞÀ¦rhSq£rhƒÇ]ʃwߪážEDDDDÂëõò×{nÇã.õkO›êÿjµÇMM›\9°ˆˆˆˆˆˆˆˆ4„êþ¹êfÍ2S=ÿ/•C@›êa  å^ñÛ«FEDDDD¡S\{ʽ¿ötÕ+PóeËê¹_û¼ÖD-F±°ˆˆˆˆˆˆˆˆø ¨ÿ.SëßZKÿÐÏU=lŸ¯Þ9ÈŒ±IMM!33ã˜Úå "..žîÝ»cYu"""­ àr¹*?Ÿ{ÔûùüKoôö:hÈþeddßæêÞáp`û|5_°´ âŸŠ¸v»ºÖ¬¿Õ·DDDDDDDDDêSgljß2û  Uã@WÿgŒÁ¶íz7’ššBaa!#GŽ!$$䨼Ðï¿ÿ†ãŸxT¶íñ¸Ùºu ©©©ôèÑSgˆˆH+h#”––°nÝšƒ~>·–6Ì)§œ~LÖACö¯-³mc Æ¿g¯eQÓ·° Tþ£XDDDDDDDDéÀ¸Þ៩žŽZ½Ùoxº:dff0dÈ0ÊÊJ)))>æ*Ùå ¢W¯$V¯^©XDD¤•´}úôcåÊåõ~>·–6LNNö1Y Ù¿¶­²mUÀ–Áª …«ÂÞZ½ƒ«Ò_¥À""""""""rûÀæ %*~¦z®²ªÛ¦z~àºy<|>ßA{ ÿÒáñ¸uƉˆˆ´’6BÕ66¬ñ/½ ÓÚë !û×–U·¡©òÙT´·-˪øI]Ã@׿DDDDDDDDDêØìwk¿áŸñ¿`UýßAø|>Õ¶ˆˆˆ´ª6BC¶ÝÚ0æí¬_rü¢ÛûM©UA°9ph¿Ö¹‚_©Ÿ£þ»ŒàÚ0ÕãAWmT—""""" f¨™V¥jJSÓà>°-®·ˆˆˆˆˆˆˆˆ4@Àþ=J¸¬T5ÿ/µ.I™ýÖ5²psö\‘¶«µ·ZÃþí}P;®Ù*¶ºCou¿^¦òFÅ|ÀUó×ߨT !-""""""""B½C@W©=tí! MMè«À"""""VÕçת °LõÐþókèg‘_аÐPN˜0žaC‡Ò£[7¢¢£ÈÉÉ!%5•%Ë–3oþÊÜî&ÙÞðQ'ø•G ìîWž¿l·_y|r˜_yñêÕ~åöáýÊß.šçWþÕè“ýÊiE~å84äWþyÎ,¿r·1¿ñ|Æl¿²§ËH¿²Ë]àWŠéäWvç¦û•3Ëœ~åÞíƒýÊŸÎôßœ‘~ÅSNãÿ÷~^†_ù‡%þõÛ¹¯}õíèWÞ¼ÏãÿzcüÇÆ5KüÊ;öê—JPwl=Àœ©5;pC†¤SÏi‹mõV;®k¶ºÇ¯UÙ¾¶òVåÃÊEDDDDDÚ&§ÓÉùçžÃ¹¿9›°°°îOLH 1!ñãÆq套òá'ŸòÅW_aëïr‘uÅï/á­wÿݦ_ƒ|ÈÔ·²·ougàZs•éýGDDDD¤áLUè[1°Eå`;Ve¯àƒ ë¬ XDDDDD¤ÍyâчIî×€U«×ðãœ9¬]¿žÜÜ<bc¢8` §œ|"ú÷çš+/gÔˆáÜûà_Uy"-hʹç´éø C@×Ú?à5Ôšø€µë|.}C¥EíÞ“Æ›ÓÞÆgÛ\uÙïéÚµ«*¥–]9ìÛ›Êqƒ‡ÿò^œÉeýìy,ßžOçÉqb‚£Ù6eì`ùÊ ¤uâW“#F¢EäpÞ¶ÑFØ›•ŇŸ|Ê’¥ËÉÍË#:*Š‘Ã‡rÁ”óèØ¡ÃQß¿ÖPG¿Ô:ø…žý5?ªÒßÚ]|kuùUÞ+"""""Òö%÷ëGnn.Ͼðyùyœ5y2œwícc0@vv6«V¯á¥—_%>®#7ÿñŽó Ç1ÿKkŒƒŸ—­ .¨ì—ùË7òñ3Ï3³´SGÿ®Y`ϲ·øóCs±{_ΰI €E¤y­XµšÇž|š’ÒÒêeÙ99Ìøö{æü4Ÿ{ïü3C‡§Š’¶Ö0ÁX•½© yH{ë‹ ‹ˆˆˆˆˆ´%gžsN9K¦þî€ëõ霘ÈäÓOcÚ»ÿæ’+®j’í.[<Ç;íýÊrý3žÓ]þkúJüç¤ìqÐíEF´ó+çú?_Pϯé?§nD‡~å!áþÏ\îWÞ^äÿ|ýâýçÔ Œ õ+‡÷ö+¯ÿy¥_yLßίPŸÿà ×îô+ŸÔkÿúñï‹9¬w‚_Ù.÷? üë#-=ǯܻ{O¿ò޽«ôËÕLÚrpd7uܪgÍ# ‰óó ¸÷Á‡ÈÈ̤sbb‹ôD‰ˆˆ   ÿ͘ə“'µú¸  €7¦½ÍÚµë8aâœN'?ΞÃ_~ŶmÛ¸êòˉŠj×€C›ËçwþŽç‚îdÆ£¿¢ö+/›õW&=VÄ-ï=Í9íÛÞEÇ¢Òr2ÒS˜pÊÄÆ?ØÞͧ÷ßÉ˳ÈwÛXŽ@‚#bIì™Ìè_Í…§"6iLk¢Ÿé{³²ªÃß ÇÏUW\FnÝØ±so¼õsçÍç±§žáÅçž!®cÇß¿*ç_|)¥•uHHýçƒ.oŽ}8ZǨÊYçžß õ¾úô#û)‰ˆˆˆˆˆÈ/×¥_ÀŠ•+ùxú礤¦У{w¦œó†•—]Ê'Ÿ}®Ê9ÊÚj\Te¼í? tã.RÉ…ËüüîèavìÜI÷nÝxéùg1¶¯Ù+ç·gÿšÿ͘ɾ}ûøòëœ5y¡¡¡­ò@®[¿ž7¦½M~^>áá\ñûßW÷‚zÜ`Þ˜ö6ëÖoàGñ»ïX´)eá6 ‰=ãR«‡¼½Ùä•Ù8‚B wJ 2ز<-+fñå7—ñÔã—0(L½qDDš²ðѧŸU‡¿=üWŠ‹‹(,, c‡XþöÐ_¹ÿ¯1ç§y|ôÉgüñºkZ|ÿª”Öê\ZZZýØú–·D;ª¥ë ­¼¦úŒ;ñdÈ‹ØÞ£9çšë¹xX,“Åì—žbÚÏÛÙ“]@¡$7™ &¸XùÍ,–nËÂÖ…1Snæ®ß "²êÂfy& þý o}·’íÙ6Ñ}Žç¢ÿÈ9}ÃûÚ§1¶lÛLŸn] Ðu/ÚI¿ËþÉkuÒ VÍ|ƒ'þ9‹]kÞå±w‡óÎuÉVÖÍòþÅ_,`Ã^¡pâ”+¹ö×ý‰$¯î™Êã ½t:ÿyÞÿã@»?àÚË_cûØûøü‘“ /úž»§<Î\OS_|‹›úídú“ÿäË »ÉÌͧÈmߟ‰^Çg&qÐÜù`ûb”²ô_÷ñÜ7ÛÉÈ-¡ÜÕŽ®Nàâë¯fRàŠú+ÚÄô—_潟6²×Aç6å@Í@%>2¾ÍsÿšÉ²Ô<ʃÛ×íDn|ôÆG銵ˆ¾eËWpé%‘——ë÷9˜——Ëe—\ÄœŸæ±tùrUÖQ¶xÞÜ­—™™Ñ*÷¿g¬^³–›n»Ÿý;–eqóíw°eëVztïÞ´Û¿-í7tEûÛÒÐÏ""""""¿XwÝ~«_yЀd H®ˆEäèûïòŸ÷?hsû}äƒÕ60‰=œ^ÜÿÐ#„¿>ŸÝbÔÚCà§Ÿ}ž­Û¶U—ÿtÓ <`½ÈÈnùã \}ý1¶ÍÏ ‘žžÁ÷ÝsˆWŽÛãÁYk‘·¼vý»Y÷æÝÜù©Å©×ÜË Ý Û¾y‹Wï½÷ó/ru?˜B¶¯Z;Þà‘»’,ØÈ'ÿ÷:Ͼ–ÄÙW]Ã_¯ aßÂwxö'xkÀÛÜ28(cÕëwsÿq\|ãÃÜÞ¡%ÿ~Üÿ ¦ÝÎØ°Ã«¯ü"Y™;8yÈIMö%GHuD~AÒFÈËÏ &:ªÎõc¢£ÈÍËkòž¥y¾°°0Š‹‹¯~l}Ë›b>žþ9ïüç¿}üÔ ÎgêSZ¤22ÒÛôùøÒóÏrÝ7±m{ 7Ýv;–e±uÛv:uâÅçþÞL¿(Ï9µ• ×êß™ªc—î~åã‡$û•Úù_§N¿÷äÊí©~åÀ‰~åÁÝüçÐ ˆô¾Xç@¿r¾Ç.eÇÙ=;÷ú•£œþSB|{¿²»ÌÿñÃG÷¿¯ÿµS}º_yÑœ™þ¯gÈ0¿rPˆǼ.í;ø•×ïõŸc8!Ö? ,wé—§…´Õðê €k ?×€ë}Í1„_}á¯mÛ-^I­9Þºm;'žž‰Ïçòê¿zhY‡ŸmÓ¥sgRwì8äó{æ?ɧ=yàoX¦pÿýl'½/y»ÏéŠ6¸3%)à¿þÌï8ŠéÙ-B;däq}q2Ž{æ2ÿ½nLnçè¨(^yéÅê S|<¯¼ô±11ÍòË:Ô:ÔZGi±ˆˆˆˆˆÈ/ÉÈáøé†ë1Æðâ?_Ñèf"­D[áP±L£.p™CUsŽùÊ-‚rŠ1D5úÒ§1N¶nÛLŸn]1M~,Œ±+ ,, |[7°ÑcpD䬉 ºœö+Ž{y ?mfÝN'ö͸®o±1uk¶lÁ¹º+,œÒ¬Z»‡ŽkRðY¡ wuöíuv`@ÿŽ8–ï$_¾ÊêÜ_ƒö¥ï>¾ñÿt9eµ†Y(£´ÌàÛ³Ôrƒ#v('¸ü·æì6šq?bÇŽ¥üýÒ‹ùfâ\0õ˜±5ßNcÍÜ\ù¹ªoÃC÷ýC`…¿"ò»)çñí÷?°xÉRyâI¦ž?…Ή‰ìÞ³‡÷?ú”%Ë–áp8˜túiGw?/½‚ÒÒRBBBxÿ·º¼É>…Š «Âߢ¢Â­ƒÅóæ4h½ôôÖ9Ttξ}Ütëí¤îØQ=çoJj*7Þz/=ÿìQïe.""""""""Í«­‡¿ÐØø‚ÄÆ†>Ÿc ÿxæéêøþ‡å‘îkѸ-†¿-ÉÙ¥½]ÓYµ2;¹KE/`ßV®É&¨wº8ápÆZvvîIÀOÙ²Ç&áÔy]‹ÛSˆ qש[ÿlð䦰ðóWxff6¶HÒi§’Ž^Éôs}ÇÒü%|5w/Ã~Mú·?°²Ü`…õ¦_—Š`;iÂñ$~ð[¾ý¬î\1ñ4â7½ËœŸ¿a® !ã&2&²1‘·ƒŠÎó…df•bˆÀyÈ}±Ø·*"œI§sù9§[ͲW+`ÀÙ#‰^ß³ª`!Ÿ—ƈ3Üty!¹Ž$μöaμl;oÝz¯oØÎO‹÷pEßî8ñ*~ÑT<žŠ+üùekH!44„Çyˆ{þò K—­`é²þï|¶mó·'žâ‘ï'ª G iL¦*ä­º]õØú–7å>rÎÙgáv»øÝùçQXXÐâu––Ö¦ÏÇÛﺧ:ü}å¥¸îÆ›IIMå¶;ïæí7^k©_ õø9¼øÏW¸é†ëª‡€n ŸýýAïÿûÛüòu/|…•ëµ?7­=Ä«[ô˜,iäúk_øG£Ö_½Rç½4¿Ãêlh™!û*æû-÷ ÿòðßZ,Vø{hVÄñ\|NWnüÏ#<r§÷0lûæMÞIíÎ…·Œ©œÿ÷0ž·Ýx.<ó¿ÜúþÃüÕy g ìH`Ù^R 9ã´dÂy=Ô6¶nÛD¿îÝ0Φèøîcã›à”i><Þr*:2ÐqÌõüejRÅ/V‡_qéYŸ°êÓ]|ÿè%,xÞ…·¨/Aô=÷BÆWVN@Ÿ8!ñ#ÞÝéÃÙec»Ç7~A‹–PF#OE»Æ¼^g"Ý;`­/dÖcWódðÛÜ=æPûâ€>½‰u¬gïŠWùÃõ³é^DJ­é"NãÒÓ§s×ÿÒ™ûôåœùF Až}xª>Úå›ÿÃõ·|AI‡:†{ÈHñFB§èÃêµ­àWDªßu}>’zõä?ÓÞäÝÿ¾Çü?“³o±11Œ;†só}â 6nÚÌ}>Ì#ÜOLLô1WOEEEL½` Àû|¸~{ÁÔ­÷Ù‡ïµÊ:ܼe }ûôá…gŸ!2"€W^z›n½Í[¶4ûö F3üŠˆˆˆˆˆC–._ÎeWÿA!"Mꈒ°ÆÄÀ¶}x}.mÛÆ¶mž{ê)þtÇlÛ¾{xˆþãÙf¯œÏ¾ø’ÂÂBb¢£9sò$BBBZýýÃ~s\Ó-͸Š\ù8O½Ì«ïý™¹Ó{ —>v=—ô:‚ç eØuOñd»×ø×Œ—¸wZ „u¤÷‰×pÂiÉ„5òÙ²r‹Èß—FÒñ“Žðë AÄÄw$jw&ùe;’.]#Y³+•Í™DÄõçÔ3®à¦“Ûéb¶ˆq¡´´§Óɯ½†Ûo¹ ‡Ã‰mû(++£¸¸˜çžz’[+›6ó—‡åá¿ÜGttT‹í@XXÅÅÅ„‡‡W?¶¾åͱùùùGííçl Ë.ÀƒÇã©îùÁ{ïLò,ÊÊÊše»•“bˆˆˆˆˆˆˆˆˆ1ëÔó¯­ÌÃLåÿ ƒ1`Œ]qáËlcc슲mû0•ÁlÅO…yÙ,ýé›:72wî,’’úÑŽ:\\ó-ìÞ³‡OÞûw£¿eË&Ž?~b£óÚ¿Þ$22‚ßœuV“„¿óçÏeâÄ“šå@ÚeÔ IDAT>ýÜslذ©AëöìÙƒûï¾ëX:Í™»t-;7þÌeS¯Àçtê7_D¤•hŠ6Bí¶B` ‹[3-[·Ó³Gwž~ìѵêû|nìþ%$$âpTÄx¶mHKÛsÐå mÃ4UµDtîܹAëíÞ½»Iö/##ƒøøø6yþ˜p:Qíq8œX‡£ò§˲*n[°¬Š²å¨Ú²°°*ãŠóÊÒpÑ""""""X¿h]ºwWEˆH›¶+5•äÑ“õ˜€–Ú9cެßeÅœÀ>ø÷Û8ìÞ½«Ù÷ùW]Ùfþ·Þªß€zضƒ­Û63¨Gwl…¿""­Î‘¶üÛ nž{ê)î¸÷~Œ1MòÜyŽ={v7jyK×QKl×®]:©EDDDDDDDDŽ¢6ø|夥¥é¨I£däPœŸAï†cT""­NS†›>Ÿ·»ŒW_z€ÌÌŒVµmuZCˆˆˆˆˆˆˆˆˆHô`l«¶å¨HèÆM¸^!"ÒJ5uÁç³ÉÈHkµû×÷Aí8‘¶£Mõ‘_žÖÞFP`µãDDDDDDDDDÚ’ €m[EDD¤íµZÃþí}P;NDDDDDDDD¤íP`9ªÔ¸õïƒÚq"""""""""m‡æ‘£ªµ·4°Úq""""""Òvåæä¨D䘣À"""rT©pëßµãDDDDDD¤­êÙ»·*ADÚ´Õ+V4ú1-»\.ÊËËq:ÇìÁq»Ý¸\A:KEDDZQÁçóár¹Z}¦9ØÖ^‡Ú?ñ×"p\\<;v¤Ð¡CAAÇ^êv»Ùºu3ñññ:ãDDDZIÁív“••I·n=[}¦¹àÖ^ Ù?ñ×"p·nÝÉÎÎfõê”””•š”Ô—ùóç•m»\AÄÅÅÓ­[wq"""­¤FRRºuëÖªÛ0={öfΜÉ:hÈþ‰ˆˆˆˆˆˆˆˆˆ?ëÔó¯­ìRb*ÿoÀŒcìŠ'Æ`cW”mÛ‡±mlÛ®üé£0/›¥?}£i€N'"ª=‡ËáÀápTþtbYVÅmË–UQ¶Xe,° ò,ËR…Šˆˆˆˆˆìgý¢ :T!"mÚê+H=¹Qq¨ÚDDDDDDDDDDDDDD~T"""""""""""""Òš$÷ïGŸÞ½q8üû³ú|>ÖoÜÈ–­ÛTIõhÒØçó©FEDDDDDDDDDDDDäˆÔþ8N’ûõS|M—––ªFEDDDDDDDDDDDöÓ·O×^u;´oÔã.¹òjUžSÎýÍÙ~åO?ÿâ€ûNgõzûß/Mæ«FEDDDDDDDDDDDös8áoS|ׇÜÙ7À•që‹)¤«ò¯{Ÿ'‡Ïeî_ÿÆ«{ÂZ]Ý•u½†îËð²÷yùÏ3™]îÐ %rMýõ—ªQ‘ýT…¿G·G¯ƒòÁWsÍÐ?óðò`LåR«×›qöeâïO`« Êtµ¼`B{eX¯bJ(ÉXÇŠ5d—;°¢û‘Ü¿3£‚òîcß–å,Nuã±"ˆ8šÑ]Ü·–eËv’îuàK8³†ä±sÖ2Ö•:¢¬¯goÕòý{ K& €¯ºêZÕ¨3æÎÍäÉg©"D¤UÊÌÌ ..^¡ºS½·b/O›®/""""""-ËØ”ÏÀ)“½î;ºZùxòu\°›œÒ8:鶸òÃ7 €ò ‹˜_Ö¤Ác˜àÁ—k½KTîzÖ¦ºê:€Áƒ†3$w6ó2²s6»îÅ;d$C»î$=¥#=úDãJ™ÏÆÒ†Ÿwõ…¼öùP¿9""""""""""""Ò*Ü{ÇŸIî߯º¼~ÃF{ú™&yn‡oßÏÉ 'œÏU“æ³âsnË¿°/â8ÆOù-g Œ#1 ‹¬53øðßXVH·k§ñȨBŠ?¾…›fØÅ]ÌŸ<…‰…ðð«™tωœ°þ.}>…\Hø¯ñ§ç³ã¥Ky`E"‘c/åÒÓûÒ?.˜àât2vÿÈ—/Ï©3Œvw8ëϤì«÷˜yâÍ\¤ó£¥ÅÆÐν–%۳ȶs)Šê™‰]H\¿Ûæ3¿r=OIGºtˆ&4Äa´+ÝÁÊÜLÊò]  Ä ÚȺmn|8U±ÍL°ˆˆˆˆˆˆˆˆˆˆˆH+‘Ü¿Ÿß0Ñÿ~ó¦{rS¾ÿ½Ã§nä¢Ó/ç‚E/ðn†Ëo; ™ñºƒ·“²h&_0ЉÇ_Å¡™Üúâv¶|ðßõ¿€3ΘÊÙ‹g²öÂIŒvmbÙ;Ÿ³´ô&dóEI×òèUé¹w!³~È"78žîÎ<²½‚ÆJ下ÎäĽÿàÁ¡ó‰:7ŽwQ1îànôì™KQn bÁ¸Â °ÁSuÜ,\11DÙlËÄ„–PÒ‘¸(¥‘^ŠvEÑ3);%‹âAgpFœÁd.gÑŠ,öÙ€ü`=}›º°Óé¤KçÄC®·o_.……­ú¸)9F”,àÃ'q #9ë‚~üðbšßý¥C/ä’îÅ”ÏøìÆc-`côËüeàNˆÜHZÞ ÞùèxF^y“þÔ‡‰Š)ÿñE^^„9ÔìNãédÙ”§-fᬵ¬Ï±ñÎBìÀŒ¹Ž’×0ÿÉyl(ŸHgº£Â™¶‚…í‡3¼Ï8Ž/M'µ°áÃ65ÇÌ1€1}!e=›J*[ËÒ´1Œ3{)KÄhÇ:{12:•Õ‹ Q#Fp\ÜçÌJ?x·îº†€Þ°iÛSR;j$111Mû|>† rй…wîÚŽ>ÔúÏuꊈˆˆˆˆˆˆˆˆˆˆ+ Îå¯óúê'¸ðÕ\3ô/L«uoT|¡–çÿÇ›gT-ÍÇ*"0Ð6,x™—Gý{%•þožù4‡sèÈɵîGffMåìa7s×Pžó˜õÅg|²¢„’Zëy"OåÒó;ÓnÎ-¼¹=|ôXv.Ù+¿ç›•ADëA¿’<ö•WÀ¾°¾ Û›.ù ˜»Ñ LûV}ËŒU`t¡ï‰A”nÞJVHB 3IßgSV<ÄÐÀFïOUø;áøqDFD_PФ¯·*L®+n+á/(iЕ呖¾—\O½út"Ä:¶¶/"""""""ÍÇ2i¬þà÷=…‰çOfLVyõ}ƒ…ýÓ“<ø½·²‡.X¦¼}½5- ÉË+ÇPÑw×ÿç7XÔu)Á•÷-Ü¿ŒyCÇ3vÈF ?…ÓÿØ“OÞͳ[«׋?û"&Gyqžò,¯œRµ´ ¯9Ÿ«_ŸÖ?Ãoÿ¾Y²ø\ñtO0x ÊñÅ&s\b …k÷°×XøB{qÜØd’ÌfÖnqc·‹&ÆÏ¾’ªÂÂÑm}«X°Û¢,±”Ò.éÔÎGDh ÅÅ^àà=€÷ïÝäb¸Šð`åªÕMþšë ÛRø €EDD¤.ÞtýkKÚ3jÒi Œv¨NZºq¶Œ¯HÁÄ'¡O'Bê:&åylY²Û3É+3E'sʯûQ4w&óÓƒpòdF'¸šfûm ÎLy…ù%8ÚÅ®V®ˆˆˆˆˆˆÈAe|Ä3ÆqÜoËò¡2³+Ú±ƒl»={%ýŸÙ,÷Ôî~kÞA×qÓñå”äbÇ_ȵg-fí'¥¸½^<<èîÜʾr5וŒ€Ë—Mú’ÏùtÉW|™ùO^ûmÝ:;aKÍVœ{æ3{v­>ÁVG:DçfÖý¼‹}iù:€-ÄíHB¯ÎÄ…ØøŠsÈYµˆå;+NW—þôóaÓ‹äq½HÆàLý–V¹+ؽlòÖ¤’iàL[Í’NÃz¼¶˜…{}ݪvûéç_0nÌ"##«—0a¼ßýM¥vÜÖÂ_P,Ò<Ì>–MÿŒ%ûˆ:‰3‡w¤êmÌ»õ;¦ÍÚÚŸ³¦O¢29 ·âݬZ²ŠM{²),³q…וÃGÐ'&S¼—]ÙexL»²¼ ŒR¥Uroù–wæìÄvtdô¹g1$ªÖ¹o/‹¦ÅÊ\h7ä×\0²Mõ6à1q’¾ä;f­ËÇ@Pˆ D¨ÉfÝÎB<åE¤ìÉgdBÓíCk>gqoå›ÿÎ!ÕnÏèóÎfh”º,‹ˆˆˆˆˆHëñï7ߨóvm—\yõ!×iZо}“÷ÇÜÊeʨêê¾ùsÞÛ2˜[ûüž[îJä›E”Å%3ªèù ƒ¼ð‰œÉz|ÊëOìÀyÛmüþ´?pÙÒ'xy÷JVfžÎÈ„‹¹þž~¬*éCßþÞ꜋úßÊ+—ºI[“Jª»=Çá*ßȶTŸßžeÍz·j•s$§&c`Ð2æÿg&³Ëua½¥æ­fÑu÷²õmúŠÏ6ÕÿX‡7… ߦ°¡2Ž´|Y¤-žIͬӿ~UçòÜÜÜ&íŸ~þùü0kv›;n €Eš“ñ°wÅ÷ÌŽü5§&E KÑ"Ò*”g°èëïXçÃX–ea—¹3.Ã*ÏVT_Æó°­4–~=þÖzc§¸¨cÀø²X½rýOìV=PMɶ•¬Ï³1X”cÓtáëÇÄÎ`ûŽl‚é}òùœR}œ<7²7¾ô úôýE„¿ 9gÁÆÞ¼)‘6hý†$÷ïçWn.N÷r¾~ ¿º¥]+—Yö–>ÿWÿÍ…œ?t,'QNIz*{ÖÑÎ2DN¹Œsc3Ù9íc~È ÀýÑ&N½a'_2ŽÙO.gö¿>¥û¥'2¾Súî\ÀÿÞK`ÜÔÞ„ûv³aß`ŽêÏà 7î¬E,úä]^O ×—:íß«·ªÃÄñÇcYù,X´¨Y¶ßÃ_P,ÒìŒ)!eþ,‹:“êú•ó²gé·Ìß’Ka©ÛLT\Ž=’>Ñ`ïcýO‹Ø™Ka‰í$¼C7úv %gû6vï+…à(:÷Í„! 5s4Ú%ìY³˜%v’UbŠŒ£çàQŒîC ‹È1ÍÎJa[¾ãˆfð䳓žöf–S¦ùö²nñj6{#°»3ªto¿ Ï~ùš#v8SÎJŒ9VÞs %Å%•#"JSÖ°iXWGZ`ç°~ÍîÊ:2øŠ‹)5a¦„´µKY²~'YÅ>#:ÒsàpFõïX3ˉ'‹u ±*5‹b_í 6µ¾¹ß1ÛÞƒÛ˜2¶þð.[+|¿>¿/é›·±#ÇÂê1œ^‘ކ}&jû °tÙr|>£Glùs¶fe}ô/ÎNc™zæÂ­C|Öš¶-^ʺÝ{ÙWXŠÇv“Ì)g “¾P,""""""M vïÞCyìégšeV?yþÑÁµæIî©Þ½0œîíløðqþpÿg±`Ú•\: À —?ÂíW×ÜïHý„wþ„wj=jæ1X¼Ç›O½×è}·|Køþ®Køý±~¬¨=üsmŸ~þÇÁ²,òòó™·àg<*¬ý–ˆ4+‹ÐðpœåÙ,Ÿµ˜Ýîºz&9±>ÖÏü˜Ÿv»IÛÝ#«x+Ë×ça;âuÎ vP´ñ[>œ·›Ô)÷L®7ä‘cà)&™1É;øn]i+¿ãÃ5¡tèÖ‡ƒÔ1¤Îo‡9FqÞù£鋾âkÊpD&qâ„$BK62ëXyÏ1e”ºÁà }ò Xµ˜][Ö³cHeëR(µ"88‘‹ÖSà.¥ 0Å[Y¶!áô>é,NîBáú`7{׬fÇ€“èQºõ»ÝG;L:› ‰As ÏÞÙ€6Œ¾'ŸÇظÊ#gö±«ö.7à3!´àH¶_þVY±j@“õnÔ9kE3xÒþs7â³vÒÆÆ9°m‡>+EDDDDDDDZÜþÃ>×eú_ª¢B°HssDÐwâ²>ÿŽ Ûð“;Ìÿ~SÌÎ%s™·>"¯©¹ÐîðR^^Çó¹:í`Kx½^ŒF‡öaX»óp»ÝÃuæì%Ëg0&“E¿Eí‘ï­¢B €EŽiÁt{6öÜΆM[ØššNÖö•ÌNÝLÊø³8½od½Ãþ–¤þÌksñvdÔ)ãèjáÛy ½ç7enVAQI$õXîÍ;X³Ø;­gÇþ Hô’a^7n윽dû VpgúõÇ´ëÝ›ø…{Hõd³7Ϧ[é>rmƒ’@ÏNƒB;Î&Ùe»Ÿ Áy‡¿ýýÃß*Mþ9{XŸµT|“TDDDDDDDD¤-R,Ò¬à.Œ8€ŒkÙ½«b¸Íª ÕžK˜µznWÆõ'>0‡u Ö’î«ïÉT]“¶MÅ%lGÕEzSqQÛªúK¯]ˆ¬uUÜ Š'\ᯈà$,.‰qIŒ›ËúÙ3ø)µ„«7‘•4’Žu<Ânfî¼­šzŽ;‰!±þï=ÇÄ{Ž·Ç.‚‚\tNîCô–•¤oÚV=“{Ó.h{ÅÐÆCÃGŸ©¬$cã3M¼Ë :>‡·ýúÂß*Mꜵ*_…9 ×r£?kEDDDDDDDDÚ°VcX¸p!óæÍgÛ¶­FïÞIL˜0žQ£FaYJ°¤m ì4Œ‰vóåšÜZ× ¥x 8Ú÷fØ€Þ„Ú!ìY|d¥ѱÄ8¶‘á+¥<¬'CúÇà²ÀWVH™3B½EŽu¦=ÛóŠ'&,‡3ˆ°Š&ñzñÖù˜Öÿ´ˆeÑg<’«¿ÈrL½çn`9 Gl?’ã×2?­Â{3°[0Vy@EËxq{ ŽØŽ´wneOÙn6¦‘Ð+„­[I· –+–Q!±Ä:¶’^¶“ [ HìÙd»Üãcboû#†cÄða­ãœ párZà-b_®¢‚0¶ «Y>kEDDDDDDDDZ«VïÝ»—¿ÿýY¶oßæ·Üí.#''‡E‹Ò§O_n»íVbccu¥ $~Ø8’wÌ`m¾]¹Ì"¬},¡Ö^ŠÓ3ý³¢\öá|åVdC{¯ç›Í…¤.˜ÎÛK‚qY^Üžúœ>•»8u8DŽavö&æÍ^I®maYc ƲïÒ…`¿`¬tû–¤¹18ñîYÄçU $l…õáW“ú;ï9å^¼¶pZ`…“tÜ@vYÙ„ôJ¦S@Eóª¢åÅã5XQ½Öo-ëòÙ:ëCvÎwâóxñ@‡ƒèæË•ÄФõdn* å§Ox{i¾ÒŠaýp—ò™`E4ßö[ìœ5íIŒ dë®2¶Íú€ô…”öæŒsGÛ Ÿµ"""""""""­U« €÷îÝ˽÷ÞG~~ñññœsÎ9 2„¨¨(rssY¾|9Ÿ}ö›7oâÞ{ïãñÇ#&&¦Íc ‹/&22’þýûë¬ü% ìÄðѽHùn ÅU¿„ #8m¬Ÿ×î$+'"GÒ!¦Ñ®ÃÝP0ÝÆŸÉí–±lón² Êp;\„Å´'Üé‹ËŒ«=={%šžC~‰Ÿ@HD =1rH|ë1”â1>ÊŠò)«¼Çò•R~ ½ç¯§²‡´“ªÑ÷ƒ:`rçZ+YΊpØëÅãp‘8ú &‡,fÉÆ]dÛFÄ“4`$£t¨lŒÑõø3˜¾”å›w“UTL™ÃEhT ±‰1Ñ^7äø4çö[蜵Âè3þòæ-eSz>¥¥îÄg7×g­ˆˆˆˆˆˆˆˆHëdzþµ•ƒÑšÊÿ›Š¹â cc*n`cW”mÛ‡±mlÛ®üé£0/›¥?Í<ì1Æp×]w“’²aÆsë­"88ø€õJKKyöÙgY¹r%}ûöå‘G9äpÐÆ®¾ú>ùä|>.—‹Î;3iÒ$þüçÛéСÃQ=‡rÓM7róÍ7ë¬l#æÎÍäÉg©"D¤UÊÌÌ ..^¡ºS½·bý‡'"ª=‡ËáÀápTþtVôtv8pX°*{>[,‹Š2Ve÷ìŠv°¦G9ÐúE3‹-⤓NÂëõþ?{÷U•6`ü¹wj* IH/@(!ôÞA:*‚e ºŠå³®WWÁ†®`ï  ‚Š ÒÁÐ{ BB*éÉ$™™{ï÷ÇDI£­AߟÄÌÜrîiS2ïœsxñÅùâ‹9dffÒ±cG¦M{žÎ; i¯¾úï¿ÿ>YYYDDDðþûïÑ£GZÏÕu‡~„Å‹“‘‘@ûöíxþùitïÞíxþ&O~ŒÉ“`æÌ¹òÊ+¥‡ !„B!„B!„g(''G*Añ·Ó`Føà}.»l4¯½ö:÷Ýw/ññͱZ­¤¤¤0vìØSÎýmÿêÕ«éÞ½[µûj:WÓ´Zó¥( v»ýø¨fqáÈÎÎ’JBÈs”ÔzB!„B!„¢Áh0àÞ½{3þ:ÈË/¿Â½÷ÞSm¸¢¢‚3f››K«V­èÚµË_ïÞ{ïaÚ´i\|ñHZµjÅ7ÞÈË/¿‚Éd¦W¯ž”•9((Èçšk®!((ˆ &0mÚó†A÷îÝ(,,"..–víÚÕzn`6Ó¶m[¾úê+ºví†aÓ£Gwé¡ ÜM_K%!„B!„B!„Bˆ£Á€EáþÅ£NfãÆ <øàƒ\~ùåtìØ‘ÀÀ@òóóÙ²e óæÍ#;;›ÀÀÆÜwß}g5uò}÷ÝÏܹóxüñÇùâ‹/xæ™§ â³Ï>å¹çžÃßߟË/øqãP…©S§Ð¸qc>üðC¦NJpp0Ï>û íÚµ«õÜú”}êÔ)Lš4‰ë¯¿???üßB!„B!„B!„hàUÅn³áçç‡Ýj• gE7 œ.%%%”——ŸYŸúIåëd· IDAT†ç¦QõÏÃÀ0À0t Ï tCÇÐ=÷u]ÃÐut]¯ú­QRxŒ«~<ëBåää0}ú RRÔxL«V­¸ï¾û ’^ þ4+W.çõý-¥"„BqFμ ¿€`TÕ„¢ª¨ªZõÛ„¢(žÛŠ Šâ¹¯¨( žû( @ÕÿÎêK‘B!„BñWµû׋Œ”Šç]`` ª¢à(¯ ¨HfgÇl6ããã·—¼ü|²Ž¥M‘§—FC+T“&Mxî¹gY¿~=«V­&%%…’’üýýˆoA¿~}éÞ½»|È%þÒy™¨pé´ µãeUÙy´œæ!6¹ý‡ÛqA6|í*{2+ˆ´Êí³¸½?»‚&þ¼Lrû,nùš ò1“’[I€·éœßöµ©•k:4y¢B!„B!„¢ðöö¦¸¨˜’’R© qÖ4ÍMiI)f“éŒÓhp#€…¸PœÏÀ]b}Ø—UNi¥.-„8.ÀÛDÓ`[Ž8¤2„ø ÀB!„Bq~Éà“ùÉ—£×òúmo°°øÔñî€Ëxà­‰´u%ÿ\ذ?›ùßYLÜõWt´Aä'&*ŠK'çT|³8ޤ§ŸÑ`UªOˆ†EQ ³È)Á_!Ä) [Ó$ø+„B!„B!ÎŒî7”qÃ8u\¡™àW1Ô÷Âø\:uãzVí/‘¢¢1 0©2ŠGQ½nq>R B!„B!„⌨EeX/C¯“—Ó|põPÙ¹¦ ¢»¿x×ÖÈZ»BÔÄ,U DÃÞÈBZ¾S*B!„B!„B!Ä9cÝó%ŸÜ̵ƒç°b¡ Ïx_ßÁW1rÿç¼ï÷0—Ÿp¼n£Ûõ“˜Ð¯1–lR×|Ưg_¹ ˜ðé2‘;¯ëK×o,‡I]õ.O}z€£æ}y––ôc{4£i-w7¿|ƒW–åQ^u]ÃI»kîàÖñÄÚqä×uj•ˆã¹ñvº•^Sà– —rÓO {è ïÐwó*Jº¦{¬%c?¼ûíraÔ#=!þjd° PZÁ™Û–ÐÆ–,•(Ä_TÒ¡2©!„B!„BqFý KçîÆÔ(ºZ=á_ÝÚË/v²jÞ6òf§TÑrÒ4™Ç;÷çêû>᧨‡xv|îà+¹ûÁvx/|œ{&LdâÓŸ1{]EFíû·wîz½ñ(·ßz?ÍUˆ»ý_LŒ«¬º°Èñ/ò|ç,zæVn¼ÿMfkƒè骱\†)–ŽíJØúö}ÜrËcLÙÕ‘«øýíÚ¥'Ä…NF јg8´‚NÏâ7iÜa s˜›ÝƒJCæ †áù…!uñ×½©…h(4Sõz­ÈÒ B!„B!.ú†ÏùlÜ“\ßw–µßX®Ìú„ÿ;`%î„#ÝA£¸®ß6ݱ†…`#ßÌÞÄÕ÷÷…w¿D Œ"֜ů{“Yj‚ÒíüZunmûý ›ü=GKg1oä F·0ÁaÐü†3nX.+žú„Å­@«>^Hï‹:×R.+¾áçf Ùúù"~Ö—.Ѳ,ëLÒâÂ&‘!! 3Ú@Å]–Md³.åÒÊç;>:Ú›ÔŠP©Ô¿ô{6GÞ‘ª°†aH ø¯*ÏêEYN¹T„"EQª‚¾ ŠÞA1B!„Bqáü]ëÞÃ÷óŽ0ñŠa´Y“Nüh?¶~°žL#âäpX M­íi;m&Cª¶f_BL©X/âËÝÏð¯WÞ¡ó†U,ÿi‹¶SQÇ>£Q=ú÷¡g›0BüƒˆŒtá2{ÖvGÆÓZÝÁüƒ'„°NóãNµ¢‚ Af㜤'Ä…FÀB4@õÚ×TN¼=·¦±³¢eÕ«·‚ah˜ŠvÓ4,–»½6ðãÑH–tÀYßÿz ÃÐ1tåÏ —úBˆÿ±-Æ0tT  !„B!„¸@¸×|Îì±÷sýi^ñ ì85\¤¸+©Ô6³ôÁgø,ßrê~×>–=q=›Ûaäþ Ÿ|ã·=Ã=Ïo"µ–}}pÓó7Ñ3émÞŸý5Éyèúø›Œ=ž°Šªš1«ÆY•‘sšž ј5LmQ4bìY$x§ÓÆç(^¾þ¡Û¿„S«ŽRÑÝn ݉;w'ÖFÇšhë{”3Pàö“ þ˽_3Ð5·ÔƒBü tÍ홆_b¿B!„B!. Šk+óçpÍ‘ìn9‡tÓ)ÛZÒö³Û} =;Ûù|©†^mJî\Èì ™5ïv^š>†ááëy÷¨µÆ}¯„ âRëwLÿt›Ü (>h'¤hÊJ%…Atl­3«é¬Ëz®Ó;†É&‰ý6ü"ú5³Á±¬Y¼„%I‡Èr* øáÛ–Þ dPÇhBíî[ÁâV°ro!t[sb:uå²KzÓÉ;m?-`ÁÊ})W@ ¦åÐÑ ‰Écÿ’ü|X¯u³Õ‹!mýøéÇŸ<|›Ò®Ï.Ô–f^eänù‰…?¯#éHåImsŠºÎS|hܺƒ†b`›`üÝ9¤n\Ê¢%¿²>íÔYÛÜó5ý+‘÷ëW|³µôxŸÓ}Ú2`LOÚêÛykvRÝýÛdÁlCsá®*À°§ß¡í±LùsF¼0‹˜½s8°d.ËÒN—\ñ:£Úz1¦­×IÃêÔ¢­Œ¾å…zµùÓ?ñþOúXú'–X²›ÕëÓ):ßM° ЉS@û›ËhïsˆDŸ4¢l¹Ø|šŽÝ¯&«7ŠÉFîæÙÇÏÕQÑ Ïâõ  9r0Ù´ò‹à>óæåtcKYK©ä¿ã·ÿtM*C!þŒça]«ZÝø}M`!„B!„¢ÁÓ©Xö<g›(Þ¦V,TË~fîâkx÷†¹éØ›ÌÞ^JeP"ý“Ù˜Ræß…!]ËIÛvÃ¥>LJäÌbM‰Zë>“)›#}10‰C*±v˨¦•°Âs]sÁ|½r,¯ÝvWÌøŠ%ÙAÄL7S>¿œAIÏuz§ÏŽ›Á\:¸Á‡¿àõoxwÆÅ#® ‘÷<^ÿé®×Ñ­]9ñ+ùúµ#äÚè5lƒ.½b!?ÒqÂö ­z‡éŽ êØ‡þ…y|ýkΨ¶´‹VÐÒw°ýˆ†QÛgjñ‰-‰n’ãé ÞÍé>r#ãKH™ÿ*_—¶ Û°A\6ÆÛ¼YžV}è¿îóì-ú1bp[¢²æñæœÃ÷`ðÈ n°þý穾-tÕ¼(Ü4—¹Uä´-QŠÕ;SN?½÷߬ɯñÛFãÜ|oÊÚEnb7ZÇ›h8U™çþ3% Ñý6´ ;Âæä&/üÚc¶Ø0 ­<WÙ1ìÁ-©(Íâá¨W±YÍT*èîJÅ„¢¨ U¢§ ØPF¦3ˆ,WTô_…a€¡K=!ÄŸò¬ƒ¬».„B!„â¤:’¼jžÖÊÁ‘ïáÎÂIÜ6é > 6¡bß7/±1¥Ã'˜Ø¾—2þ¦Pšx»(OßÌ//}ÄwÅfŒðš÷YK>çÕÏæ¡ 3xïæcdl]Ì/Ûº2àøu‹Ù÷Þ#L½évnx|´4v­=Bšîs†K÷žëôNîÕ—–­"ñ/ÛÆÚ•IìÌ6¡gÙkƒ['ÂO‡°ì‡_ö'¤³(õjLHßH¢¢‚áP6»„â}x‡’w±«Ä—Ø–íhN³‚½M3BŒöï:HŽ^{0QoÒ‚V¡JN2`Æ?¾Üd­û‘ïÖ¥Ä8L–aÃgh<‰mbYžæ š6ýǺúïGÝó sÖf×}^z9M"Âpe°gëVödº0²V°28ßø°ÿyßóÍ{ì9­†s㪬Àápü>Gm?S¢éJ³æá¬ÊÌ:çå•° ÐoS@7µ¥ã¥ºñó±{Öz­ÈÇå²É&ŠÙîyqvÖ†À²Žm¢4c'þcp§¡è.ŸQ]%øÙì´±ð_‰aT>Bñ? ưB!„Bˆ Bñœ¹¨Ö?rsY÷øÅ¬;a“¢çrpîÓ<4÷ÔÃÍ™‹ùxÊb>æôöa‘úÍdîüæÄóøâ„@´Z‘Ìš7ïgÍ›žûîàxvh8eÏ1ëyR>—þkKOy)îe¼ý2À«^éOº|¼@­tਚõXu¤r$-GhÄñON¦Q^^Ž£Üy|¯· ŨÄétƒ³’J·Žf¶àÓ–v‘ zúN¶§×õ…èHBLnòÒ‚Ú”°°ìÎRÒ2(5<×.NOçpa4탛ž°wP(òP}lõ;ÏØMe¥ s£`B›ã»/“bÕ_ÍYxVu:xÊ "³ÓQL·jN¸šCÊê…Ì_²‰¼‹ßfD 3#Z˜Q‡aô®EŒŸ:‡žÍ¡]æ\ÞûpÎ9i×ëÞ|ß ß±cѬÍ1£uãâËÚ±ø£ëÓ+pMçˆ3’Ĩh@ÀBü-ü6t¢×Bü-€‚nh(&;˜ìžÀoUØ@]£¢à•º7¡ñ=ÉÞû3yºNx»Q˜¬&t­EUQ‹«˜6ö½,+í!-„B!„B!„¢á‹kFËü­,(67Ìôj¡:J¨t…coÒˆ€F*è¸Ýn «õ')¾„„4ÆK)ãȱ Š­D™}ñññBqûàcRP¾D´nJ‘Ë¡ü@Úÿ7#Û™)Ùü#ó—$±-÷ä™# S3‚ƒü0™äå•`˜"ñó5cèE•¸0ªÂ†Ji1Åe 5ò=~î®·'±ë·t,ëq^ Ùû¶³µå\yù-„Ä$±½"Š–•d®Ûtv•jŠ¢I“cè[ðñ798b/â²ÁýÒ§”éŸÜÆÜ®cPN˜úÌNÅd±b³ÛЫâê†î¦Òun¦V+s9V b„…œ—~'`! ´' --ûð²†`` ˜lžQ¿/T«?ŠÕÕ⋆Ž+w'¥eVìV3~Á±T–ºîC¬14ŠìˆMÑPT7Š»?£µ˜bÝ_*ûO¥îE¸³œmyú=†÷½Û#p–Õ¸þ¥¢š0Lf&½™Éb“¦B!„B!„BÔÈÕŸË#±agÅ}¹êúV8~ø/[ÝJƒHït¨¥k8tØÒ„ž ¸¨Â•G(ëÁÀ]éÔä`µç(aíhßÂ˱­ì>X¨lúõÑÍBh‘Ø™ž%ñ4µ;ÉQ w޵YWÚÛ¶ðã7.B:w¦_¯"¶-øÃ¤Ç¦Hü|£”R‡‚aòÂlÕÐдêBsãvƒ¡˜ªÍ_½Ï+Hfë†MÄE`øÅ×ѵü0[šË¦½ygÛC(9²‡CÛ¶³/SÁÈÛÌÆØ@º…Éç¤Ý´&=5è".žt| E›ç1áù¹ç¦c%”–9A=?q Ñ™U…psf\¨f;ŠjA±ú£Ø¡˜})+/'ÿP…‡Å·Õˆ5´|ðõþ=¸f$#}ÿF2SÖa î@c_;fÅ¿ÅI¼%™Í•]¤²Ï#Åb¥w—ÆŒjéM¼¿ ‹®““WΦyÌÜUI¹ÉΕ—DÐ|óa¶å]Økø–—æqó+û1\eè.†«Ýí—Ýí@w–ñý'Sp»Ê%,„B!„B!„¨•Ù7–ı7qãƒ^˜ò’Ù±è_üë»RÜ $½Ób䓽y)‹, —ŽÇ=+)Ú³‚Õ¶°¡Ôû”Ãu{,{u¢õ([7ïâ`¥ @ùϯ±"¯;£.Ç[ ~J"Ó/‘Özûò BÃ\TÚÅ–$'Áqô ‰?¬z«Û½±X@q»q¹­MWQLfÌæ†(™­X- êÕ×PýÎó¡Q»¡\qQSü¶¾Æä÷ØÛã²!×pKP½¹ôœU±â®¤²Ò…ÓÛtÎÒ4e­eÑòïùbeæ9_ØÃËíÕr~Cò4"DÃÞÈ‚Où>Bü¬¨äçs¬ ü¬Ã”Û‘ƒ 7Ýlñ h†win³nÍzRZ6ŸÆ(.3YIÓ섆ÙØE¢y§€Ï#ſŠ—GrU€›õ;óy#ÓÓb&<ØN¬œ±òêšE5ãH]…î*;õÅÒæªI:Æi½1,dË7³ø5p ·ŽD•¹Y,{óuÖÄÜÊc—Å þñ¾ÔB!„B!D½({?eÚýŸ6ØôNûúήšÅ««fUmñ"¢ß?ˆ ­<é8C ¤iÏA j^Iƪ•¬>èä·¡CŠ;Ìi¼»Ñ³x²)vW íÈ.ö&ÒÌÐ)w8(Õ+(-sá:5¸¬¸54°ª˜TôLJËZ¢„6ÂßÏŠ’황ÒðòÆÛîF/-©¾@õ8O÷H«Öø•neýúݤæ™`Ù§¼Wpã·N »]nUÅl6<פjòä×]SÈþ?¿¦¡(ÀÙŒ W1©&”ó”w Ñ¥8cÚ…·êfÇ–µìJÉæVbV54ÝD–† ªBc»7^XÐ1¡& ÅrüÉÎÐ 4ÀÀL¥©1¥¥ÅdìK'r`g‚õ#˜p£Õçi@1Ó±k7¶ó¡©7ä–ñýÚ\¾Is{^|3]{6a\ ;±þ&¬šÆºåG˜¶Ç¡˜hÛ)„ÚûÐÂOÅpjd敳xe rªyb«íø\}ú‡16ÎJ˜Ÿ ;y¹¥|»"‡YUS(×#¯}ú‡1¶©p_:YéÅÌO1h›àGÇ3–J'›¶äðú¦rŽ¿¼©ºuf\+oâ|¡(§ŒoVæð}vuS7+´éÊ•|þM:³³kݫк_Söó¼H­ùáÏ&›j¬Ïˆv<ÑÓ›P»‚»ÂÉÖí9¼¾¦’†Ðq CwU_J“ E‘ð—ç]Ì^ÿ?÷xŽ9÷vÆf88ºc i¾éÑÌç÷· FIó¾à‡ÞqËàÈÿM–íå«ÿ¾ÄG+ö’]a¦ÉÈÿðõ¿»ST]þþÊjj“:Ï+`çÊl4Þó¼ðÇûâpR”y”Bk$±AV©!„B!„B\tßf4 ÂV±ã÷Š/¡]‡sqw_Ê·|Çâ¹”Õ2e¨MhÑ&†=›½»“ëÛ§¢âeµ`U4,…S?«U\%¸\á(>6lVPJ’““HEBÑÑø¥¤Sl¨x…GåmP™–}üÜ6·¾EïF»Q·.äÝ%uŸ§û´Àn³a?©N É/«~ÝÛ£©7÷#4<œ0s!GÝž «58„P/=» þ•¬(çõ³L·æ‹ÅbÅf=ë(6l6 eç%¢2« 6£”–ƒŽ×éq‘†¡¹T;ŠÙªÅlC1YQT ˜¬(Š CQQP0t†»?w±î ´ªéx5§ÅdEÍ]‡M©ÀaøÖõ,D«^‘ü§,_›ÅGy›Ä—EbûúŸe€‰M}+Ìã¥_Ê)ULè…n ZöˆbJ…¤_s˜š¥¡øùqÓ?Zù9§^«ÖãsMÄDz˜wŒÿ.«Äi±Ð½K07_Bú§ÙlrÖ/¯1‘^æãù%¸ív.íÌí}*ùq]./®3ˆkÌm½Ã—u˜÷Ž€JbŸHméâ땼Ub¢S·&ÜziÙŸf³ñÃyÍÞ om¥8ù(ßf×5µ³ÁÁÍLßíFÇÀQR[}Baf-. ¯‚¢ùg0&d3%ëÏï·:Þ͇ƒBÕZÀU?Š‚¢¨ø†ŸyÚŽC,5“/–l`ÏÑ"4ïPZtÀUnà²6ÂÈJýïÝÀ5ŸÅòüÏ0Ô»ö£Ë—ý›ÁÿÎæ¶/ßᆚÄÄ×Äדo÷n>~äö];›îg`5(Ù3ŸWߘòi¸,„5£ãˆ[˜üÏž׫‚Üìüð ^ÜÇÿy•.¡Â‡·{7¯œuþþ0Ÿ¯ïÍ ö³üÅáxŸÕ»·sÑ&âTìûq‹÷—á6@Q­Øý—@·nm‰ð®GMkÙlX´ˆÜÎ㉠²JÛ!„B!„¢áS| ˆŠ "ÀDå±<ʼâh?`0"³Ùùãæªcl£/ŠÅ7e1ß&ePl²c7蚆ӭ”¤9¦-mC ÜGv²= œ¾¤WÆÓ5.ž„ŒJB"͘‹ÒOÍŠ;…‚¢fè ð7A^%ùÉ»ØÑ:žKû]ÌhçVçÅÐqpâ´ü¼;íø¹•Åù”RŒêpuŸgÎ]Gjú z Ȱ•¸Ví'] ¥e¯$xUÿvɲÙlU{ryQ\‰™Å›s)ñmMÁ]‰wmaÅöCõ¨prG9n¯(¢¢¢ˆšþáVÂäÊ%û¿§"g—§ŠÊï`ð nEQNÚ¥km`úòYf #®¾™´ÁT˜Âšs˜zË 6Oy›'‡üƒÀ*±­âñvîeß¡­«Z@Ûˇ÷=džÎðÚ„„ªYÔý)8:Ñ:Tµc§½ËØs˜£àg¦Þ3ua—rË#·ÓÌ_§ u7»T_üê[1z›·d6øq®Ðî÷7.yNçŠNey9zxw®è‹Esâ(Hgdž|•VÂ?Æõ&BÞ™ !„B!„â/§¶ Öt»ä"ú6õÂìÈ!mûJ~œ½Ž¤Ï'Ö®¸«éÜ-ŽžâhÜ¥½¯®:U/ eéxåñÔ 5” ÑèYìÙ}„"CA-ÙÃÆ5\z1ï†âÍß3oÝÎS³bdr,7W³Æ„4 ‚Cy¨%{X÷½Få€aŒºò>ú› IOZÌ¢eIlÎüý³ï”/#儤ê>ï‡×~ÇW1ä*î䇗»€Ì=kùùÇ•ÕÖ”Z¾]Kâ,ĈA·ðàeVÔ’#ì]ÿ3ß-ßÂΜúÔ·FyÊÖE†såÈ{xºç¯ÜøïO9jãOÛðíØ…Q7ú±ð±÷O»U·}ú:î®í¸|ÄÝLUBÖ¾t JsjLÿ÷€P ÔâœóÒëäc6! ´'š¦ƒbâšÀbG5YP¬jU ×0~ îþöã®Úf`è:è†b`¨Uóï£x~LÜNW½¦$5Úifv³>Ýu|­t;2Ü\g#R-a¿QÓ¹6š›Ý$qÖëû3§{<€^ì$û™çµ Ôa5ÓÈ ž9³Ýä9 ­MEÔq•Ð!͘7ä„üšÀ飢 \—5N-¡Ð¬{O4uðìWyì?íž¡Ö,ˆ‰]}ihƦiTš BmS+ë•Å8‹3ªÝgñnŒÛUq©–³ñÝç˜u4{Þ}™ -lUÛû1ôâ!´}èŸ<ûÂkôïúCüޱìÕ§ye2‡³ ©Tý‰n7€±·ÝÎØö'Œvg±êÃWy÷ÇM$çh4NÀ„ûïã ¾(zKgLå½5)¤gRŽ7MZõeü}ÿâÚv~'µ«%¾Í”eì=P‚Ñ:ÐöÿÂwI{IÍXÆÞñ ´5FÉû3Pã¯!Þ è)¼3~ß÷~‡¹w¶© Þ»ÙöÊ•tzÀÊg—ðÒ`ƒìždô·Ùd9,×÷Íl. eÔ´‡¹¡SÕK{ŸŒ:©Ž±~æË¼6w-ûó‚Zôæê;ïaB·àªº©¤¢RçÈgÿ¤Ëgž·mÿïs>½¶ºü-⪠×p׎ÑÌúìVZ™çzžõ i·ÌåÝ«BP1(üî_ ÉÆ”…ÏÐú§ÉÜóί-rbˆ¥Û˜ÿãß·ö¦‰ èy¬~ïEÞ[ºƒäŒBœ–`.zð¦]ŒZ[{ÕÚ!ëhKÃÁ¾ù/óÜÇËØ•U%0šwNç™Kë꣚6j%un-åõ{&³Ž *–8ÿJ>œ¿›]=ˆˆ1^ÂÁ¤U¬ß›Î±RïÐætëß¡¿=þu2V~Ìô•ž~ÚââI\Ö<“¥Í'·óxÆun„Å[˜óÉfBÆL`pd%‡ÖÿÂúäLŽU ™¼‰¿h,—´68°r ëåQTZŽ ¾!MéÒ ÃmÒXB!„B!„8{ÆQ²·eæÖ̬áË¡wùù}ø¹qHEÏfï3Ùë¹WµÕEÙÁÌuók=[£øH*©‚h ¿æy²X¸ŸMó÷³iþi­Žó”ʯû’·×}YÿDË3H^þÉË?«ñŸ»–ŸO,¿~„­ß¾ÏÖã×=ʾ%ïñì’ßÏYÿÌØãƒ«ŠWmþ IDAT>Ï‚•° šô çþבMSÁzö.YÏsKªßÿÇôO¾o# &šHS%ù‡SÏK·“° YUÐÝšg¥n€¡cœôcüáþémGQÑ4ó=w¦¢xÆ&kúù9<ƒ˜uÔ³(‹[3àÄ4 ·ŠRUE `¸Y¾ô(_fŸA®ph§ÒõR'Ù.H¶aÁÁ‰3D›-&½Õߟ|OcaP5(GF ïÊaÆŠJ #†„Ò¹¡¼ÑœøDtö|a¡j½ßEŸ¿€³¢¥jâ‹îˆ:¹ÍUºw~PT}¢™¿8‹rM‹?c,QŒùç(>½ù+æ¯*bðÅÅ$oØLv³[yöáÖØ*2HúæC¦ß•LÉ»osk++PΖ7îåŸÂ™xÿ <Ú¤˜õ½È ¼JÄ—“éëUBÊæ­ä5¿çmµ<•Ÿ¼ÉKX‰ûj2}N˜WXm’@b°Æ’]p_Ú É+VQØ{8½¶¯ä罓h›hí»ökÄŒjM#¥¦67Óêúi<}i(**¾a¿—Õ7ˆÛÇu¡‰’ÍÊ™¯ñÒ#¯’Sx4Q¦c¬ûq-™‰ý ?eiÔJv¼}/wÏQyçSÜÛÌàÀ¢wxíþû©xç}îhóÛ *‘£ŸfÆ5Í0¡`Š@%·šüùác´Ãúý.v´j¬ ¥ngG‘›œ{q^‚»·ïÁhóO:y+x·Ã=S¯¥‰ŸAΆY¼ðöSLoù5Ï òC¡]+W“} Ï<Ô­SlcÔºÚ˧¶Y{[öʘÍã/®¡É-OòA¯ôüTJCƒNI^}›ÕZq&LvVܸݞ?VŽ®™ÏÂý~të)ƒ}+Hݰœ_®Æü`šš<ý´I—KÑÆ›_}ÞÎ:È:xˆ¢€žŒ¼(»^èBÇÒâêÍŃ›`rrpãZV|¿šÆã'Ë !„B!„â/F-ÚÏ®”š%´„ã!Sñ¿¤%¡E–ò½ìN.„ó0¿¥€…h€ÂYÐ 5Å„^ÀõLë|âOuÛôãã“·|¼¢¨èºªQgX+¨à;€Ä( j–gZeT m#ÌT«ä¨NihEN2ŒÂÍ(Yu8>ÝãÏe^kM·°’4-€fµ·£“]–ÔèÓª1ö•³° †’:•n𶩨€VG²Ö`;±J9o¬/fk hdTÐ À>Bùæƒ)ž¨ù LªÊ„{1\eè.†«½j=jÝí@w–ñý'SjLWÏM%µÌD‹6-©.cŽoC+›“}‡3Ðñè|›v¡_ÏÈÚ^½Z Üp ³f­çº)ýñ.^Á'óréýðÛLìµÛúáLÖ\ñK¶>@ß^Uå‰ëDŸnm0Ñ…n!™¬»ùGVíuÓ§ó /›æthcå˽{Èлkìcñ²|zLºþ–ykénîHl)k/{ |IlWëTÝö â›GýþR¯(4Jèψ¾žòtJgÍM?œ’5êJþóèAþ£W¶¢ÿÈK¹òŠ‹éåU5r%3¿N¥åMŸòŸ«ãPn¢)K¹™Ÿ­æ†gñÛjà–ÀHâ›7ÿ=¯®êógtêN;ÞbÓö ®h#wËV2||aûföºûÑQ9À†­¥Ä_Ò…`UAïÉEUç&¶òçÀÒX°#mPÛª7# ¾Í»Ñ¿[›ã×6êj¯>uGçjjËz>…†½:w¡}+/ UÝmøÖYQ_†»’Ò‚£ì\»›B¯¦ô 7AÅ6í(%vЕôjá@“‹J8ôñ¯ìÏHÓ誇ŸO ÁA~J×êsE[P4Í¢CoSóÝÚ8’¸èPT¢ˆö-&uÎ^æèÄEÉÐn!„B!„BüÕ8Èܾš_ŽÙ¥*þ$ éänÝËŠŠ,RJÎÏçOòY¥ PZ“®ºÛ3]óo#wõjF÷êg8Ms×ëK%Fe)s·;y®[8w¹Ž±,O!.!ˆ«ƒœ|»¼ôøšºÕžë(eQruçVW«ó!¬©q*l/_îJÂÁLî]á ¼ŽãÏg^kM·¼”ow5fJ—pÔóYšáÂe±cw²tOå,:IksYÆ­WEÓlK!I9n*­‚N¨tÝEÊ1ƒËZ5æÒì"R1ã_^ƪ¬êóáʯ$ƒFt÷'o%Eº™Ð2Cé-oU?õó›ýPT3ŽÔUè®S[ÀdóµöŒë÷E€޲¶¤w— >Ù¸‡#ZZ¤îç@¹ƒ£SGÑýéã Û¥b;VVm*jD4‘J…ÅšîE»-à­íì,5ˆ8ôK‹zp_Ï0º¸»ðÜ‹Ùr{;÷ì YMàòËY׳EDµy±Òô’ǘ=øf¶-ÿ‘ùó?徫?¤ÝÍÏòÒMðNÝ˾Š&\Ôù„`¦)š®Bx{ÍRµA$žæ—#” ôm=ƒÏ’vãЂMÒöº𚵄¤TöÖÍlÌŒ¦OïhTœ¤¯øˆW>YƶÔcTXý±”i˜Û9k½†Vg{YOë;'¶¥¹÷Lì¶‚—þïZv ÃÕW]ÆàVu¬§}få's§üÈ«/ÿXÕ‘T|ÂÛ2tLZxž™K®ÛIñ’xù„ést,eÿ›zVýðW*(¯4¤±„B!„Bñ—¤–¥‘’,õð§ÕÁVœßk4èpAA¯¿þ:ªªr×]wáïï/½Bü-˜UM×<£)ëõ[ýHàÚ‚À†¦aÔ+e°wÝQ¦¸C¸¡K8S½¡0·Œ/äòuVczIZ~”×]!ŒéÎH»AF– Íð T8yµÜº?y­ÎöUéL-áÚ6!Lî¡‚ÓÍ¡äc¬Ý[Aù)壴„ésÜìéÖ˜aíBè«bÒt J\l9XA¾á)ïê59´ÌõûbvºØ¸¾‚Õ5€µÜ^Znæ¶®!<ÑÑ„ªé—V²«X£A‡) CwU»K1ÙP”š¿‰ Çã­±i÷~œ£ºñÇx·;eÉ•¢c#PɯþªâyœO4ˆ‹Ÿx…›[ŸêSñn„RMŠÉ„ ½š©ÉUB»t%Î=ŸÍ{Ji¼ì½¤§ŠW¯Áô|a “nEݶwË«éÒèì'VÔšòRµßFÇè8b,ã>¾—‰ïLã³~Ÿrëùè!j8}ú6çµkØSRÆšQôþ¿aølúˆï“2¸Ô¼–ä°Þ<ÜÌ„žò)=ö%ê˜û˜ú@Aáë§ç—z]§¶ö:Íú;±-­Í¹æå¯è“´ˆ9³fóŸfñùí¯ð΄jú^…žòù™—CgŠîÃÕýã0»²ØðÃrR­ÁDY?£øÐzèzœ´°²‚ŧ–o¥*žiüõÓYC æ*ÆIOB!„B!„B\Htøƒ>dûvϸ¿Y³fqûí·ÿíH×uÞyçš7oΰaäÇþM„7² e»áÄÀµŽî=u[íÁaÍ`®oPÈp³%)“-I5í¯döçÉÌ®n—³’Ÿ~I秪‰Ú¸13®mD¡Ã3²vÆGê}|µ×©,fÊëÅg•W×L.ý¤L0gv2sNz0ºÙ””ɦêÒ­¦º£œE+޲hEÍUë.,æõ/‹yýÛ«¯Oƒ;³yhgöïY5t ·«~³ŸþI t¼›å·@yÕ¢ (*>á5ŸìÕ•QC›ðãâùòßì„)ÝGùú÷å¶¾ÕoÑÓÙ¶=[óx¢M`ŠŽ§™uûèDhŽåÔ*>-¦¦Ýèü k—JÉrüt7¼üûpI¿é<1ÞéùDõïJDMqnņÝ%%eç0LëE|ÏN„¾ýG24LhiûŠÍ›¢·‹õŒÖÒØ´5{‹ÖÄšj){ùS‰xñï-ä‡ÙùlhÜ£BðéÏŒå øÆ´›°wÒÚ •öpÀèÀc·^BtbüëßÖÙ^gKñ"¦ÇU<Øã†¾2~¹€­×%У†2׷ƾ±bèI<©Ú­~„„c!˜¡# ™=o5‹7‡ó®Á˜‚ 2m#·Ð QëjFdëf,f¨¬¨Äà„Yý/|¼ ¸ 7羯!„B!„Bqi°ବ,’’~=~ÕªÕÜpà øøøœók†ARRþþþ$$$4¨zÐ47Þxƒ›nºYÀ#iù•$n@­!€{Ó>Ÿø£9Ñu·'àc:Ÿ%Qˆ µâ$£TC·ÙèÙ5˜¢"ÞÊ5ÎÁñâBP´ç;œy{ñ}U~ƒWp+ŠrÒj9Û›ž·=¸í0cÒ­ì{‚Q RX³`ßîòbÄ“w14P© `êd¯˜ÅûÑCi ‡ÀûÉŒ¹»gÛ€\ÙLîøô15Mdt‡P¬Ù¤E1úâvœö+Œ%‘½øâÛÏÉŒËûmÇóÝë’Axß;‹¯´p®›Ü¼æ‡š)†„x+Ÿ/žÉì¶WÒÂȤ °?ÃëŸ çŽYLù®œ][hG/:̪9ßrØÞŽk[™Qüûqã?bùçGÿfŠ÷­\Ú ’½Í›rÝC}¯ÿ{Zùkç3ˆá­ÞçµOrˆÿ.ñ&¥ÿZ¼õ3&ÞÓ3 Ä5'Æø’¹ü@“¡ÍilÊ!£´Ï uµ×Y ªÖÓWóÕ&ƒøMðrå°á` øÐHÔêË<¤®r(ô7ÈÜø3kÓ¢éù‡ûÑ>(ò”pkdO†wIcNÒr¶4»‚®›Ó%q#ßlúžïÕî´ðÃä*áXE#Ú&„cUilfó¾l kO0Å8¼šÑ:ùIII8N¢££;öjyäÜn7×_?ž»îú¿O&Õ3úÛPÔF÷ž0²W¯f:h½ši¢OØfhžu§¥œ¯„J“p_.oi%ÄKEu¹9œ^Ä KóØ«‹ãÅ…@«,ÆY\ýÁïÆ¸]µž¯ôäÁ÷> ý'ñÅoñøG%hö`â;âÑ·'rE»À–³V°YŠXÿñ3¼ŸéÄ/¶ W?s?wtñªÚïC·{^åå€×yû»éüë½2ð ¥åà;<ò ÀXé4°Aó¾#pøHOvhë8’áaóøDëÃE­ky¹U1ô®Ùôä[¼ùðrÜ>Ñô™”Ȱz€ \ÖFø,ã“—>#£°Õ;ˆØ6xàåÛ¸2\ì´¿m¯Ø_áµ™ÿáöjÑ››§ßËÄÄ:’®)íüQÔHFŽêÄ[{ò:¸…'ÈyCÛ¼Ín÷%Œjá {›Z癇ŽñüÌÜóE)šÅ›FA1´ò«# ZG{ÅS—+ÿ?úÓÓ‹pšýˆJèÇO\G+@ e¾ªŽr¨á\róµ¬x~Óçõ§×Ýmÿp¿]ÞzåOz+Öu’çòëê½´Õ†è~c㵆µ»V°`½l~„Ä÷¡Ek°*vZö½ˆôŸÖ°fQ º5€¦½Âhn'¤ËFT,gmÒböV˜l>4 ‹¦‰·*Õ,„B!„B!þV”¡ÿ˜Te5ªþU­jpÒz¡º¡cèžûº®aèº'@¥ëèºFIá16®úñŒ2áp8˜<ù1ŽMÀb±¢ëšV{Ä'88˜§Ÿ~š   Z;vì]»v#&&†;î¸ÀÀ@’““1™LÜ~ûí8NÛr×]ÿÇÝwßÝ ÈårÑ©S'nºéfî¿ÿ>é± ÈÊ•Ëy}ËsŸ°aн©7 {oaÌÿÆ]’Š¡kºÛóÛЪî×´­jû)Û4 Ãs¼Å/šåËWð•}*•æ óçÛoS@—d`õ‹—7¸ü½9ÑK®ûf“E5UÆóE„ªiÈÍ~…[ßÍ=û‹é)¼3~ß÷~‡¹w¶9¿ƒÛ…¢Jßçáb¶Ôº¦yuμ ¿€`TÕ„¢ª¨ªZõÛ„¢(žÛŠZ5e¾gÚ|EÁs¿j&…ß&ÂVäµ\!„B!N±û׋Œ”Šç]dDÅÅ%¡ëºTˆ8+Šª¢Ј’Ò²Ž¥M‘§•FƒˆR\\|<ø žÑ¾õqìØ1222ê ïÚµ‹¢¢"žþ9z÷î ÀðáÃO9nòäǘ<ù1fÎü˜+¯¼’>øgžy†üü|‚‚‚˜0áF&OžŒÉdB×už}öYæÎGjj*V«•—^ú/ãÆ«vûµ×^‹ËåâÅ_ä‹/æ™™IÇŽ™6íy:wî @EES¦LeöìÙ”––ÒµkW ¥·ÿÉu`à –Õºžoõküþqû)ÓAë. tMÃ0Uë² qîÙýƒ™ÿñ³5ÉÀ00YlRQB!„B!„Bˆ –£¼«Õ‚ŸŸ/EEÅR!⬘Mf||¼±Z-gžFC(HXXýúõcÕªU§u^tt mÚ´©ó¸˜˜Ìf3sæÌ¡K—.ØlÕî¹ç®»îÚª´£èÙ³|ð>5bÅŠ•L:•öíÛ3zôh Ãàûï yóf̘1·ÛM||‹·<ùäS|ýõ×L›ö<‘ü÷¿/rÍ5ײqã|}}yä‘GùòË/yâ‰Çiݺ5ëÖ­gãÆÒÛÿN U1ªnjõXç·îµÿ64—'}Ýí cÈ(`q^ÜôÊ!©!„B!„B!Ä_ZaQv› ???B‚K…ˆ³¢N—‹’’’3N£Á,Ewë­·’––ÎáÃõ ñè£`2Õ=ÉgÓ¦Myå•—yøáGX´è{ÆËÍ7ßLÓ¦MO:.4´É)åÄÄD= 1vèйsç’”´Ñ£G?¦M›6 0àøýߦ®þãö‚‚>üðCf̘Θ1c˜>}::tdíÚµtíÚ•Ù³góøãÿæ¶Ûn OŸ>Ìšõ™ôö¿‹ªu¯ÃüMc€î®çèÞºƒÀFÕ4î†î]G7œ†~ÚSV Ñ ©Í™4k“¤&„B!„B!„ÿC†®S^^Nyy¹T†hLÔÇn·3yò£×ylhh(S§N%$$¤Þé_{íµìÛ·—gŸ}†Í›·Ð¥KW¦M{Á³Æq-.\Ä!Cˆ‰‰¥uë’““©¨¨8£2&'ÀáppÇwLPP0:tÄét’••Í)¸\.zôè)=óoýB¡q8»¸*`ë®!°«Ÿ<ºW¯f:h½š€1U#‹µª) ]•žµ1¤â…B!„B!„â/ÌìL-\ÀO ðÓwóøî³·xgòU ‹úý˜‘ÿÅ—eÝd!.t憔™ÀÀ@:vìÈÒ¥Kk=®S§Î§üý··7W_}5W_}5/½4§Ÿ~š‘#Gкuk”j¦¿Ý½{7'NäÆoä…^@Q&MºíŒËgªªòÖ[oÒ©S§“ö………±wï>Y üoÎ0tLh”Wh8‹Ò1û4ñiu÷ï¿ ÍÄ=¾ý„}ºËó[ól×uhnÐÝ(†ŽŽËQº†S7a2$ø+„B!„B!„öí/pÓ3›9† {h½®¿›žPqÜ;›Õ©׳*­D*Jˆ \ƒ †Á¦M›ëÆà3Y½Í‹Ý_¼Æn©&!.x *œ’’BAA~ǰwïÞSÖë­IRÒ>ûìSú÷ïOHHùùù¼ùæ[xyyѾ}Ìf3mÛ¶ýöî<ΦúãøëÜ{çÎbŒ1«}4f;3„±DE)!¿¨´Pi/´‡´¨P”в$$Ñ*)-v…¡È6 ÆÎ`f,³Üåœß3¶2[ïçÃ}¸÷Üï9ç{>çÜsîÜÏù~¿L›6zõêcYaaaT®\˲2ä5:uê„ÃaçÀg¼}¡¡¡Ü~ûí¼ùæ[Øí5jÈáÃY¤§ï§[·nsï½½6lMš4æÐ¡Ã:tXGêåt¶;Ø~ÀdSé»yk÷a,Ó{tlàs³ðaô§X±`lv þ°cÇADDDDDDDDD Ípåë=HvVÞoÄ^œÅ=KÛsçW^Z¿ö>M’æs°^K®ŒòÃØ±˜Y£ßeÜŸn,Àr”¥f·ûéÕ<†(¿½lùu1›ªT'ë•Çyo›S¹@Š\ø„Ê9´oß___fÌø·Ûuô½Í›S öõu’––F¿~ýÙ»w/$$$0cÆt¢¢*ð⋃èÝ»7Ý»w§xñâ<÷ܳÜ}÷Ý :”aÆñî»ïât:‰ŒŒ :úŠ3ÞÆ—^Lhh(<‘W^y…   n¼±#·Ür †aðÌ3Ϙ10dÈ|}}‰‰‰¡J•*:Z/‹«­aØñõ÷'¸bÅCNK™2e);ŽàJÄßÚ«6çñ¾'-eÙ£¨Só[F¾÷(cö†P¡Ë³ y¢3)÷MdnN1ÊöxWk}ÅÈ—^ã×Ãå¨vóãô+»ï`‘ ªH%€+Uª„ÃáÀãñŸÀm·õ lÙ¼ÁÆ7nÌøñHJZŽÃá jÕ¸B/·víÚL™2å_×}²±‡{ö¼ƒž=ï8ùéÑngÑ¢……žàããCÿþýèß¿_Ë|àxàtt^ÆJ—ðaë~—!""""""""""çLvݘôuþ ËËÁßF2य़Ùàµ0‡›s§óã_,2X9y&?_Ó„„òcùiWn¹&¹?böF'Éüñߨ"^¹ÀŠT8&&†Q£Faš&¡¡¡'¼WºtižzêIÒÒÒp8g5¯HQ·5Ý…Ýfà5-CDDDDDDDDDDÎ ÿ•/Ó}ÐröJÝ~cäÞɦöBÏoËÉ =Ç—P‡…§l q¶U|¹ñ¸T“~Ò)E­Bÿ–Ø ×^“KÞÎ 75Êúà´±rkÕJûãtçíy•H?ü6VoϦR¸¯žŸå󊡾úÙX»3‡ò%çõy¹`_üíN~K·ëƒ#"""""""""§f¹q»Ýx¬],ž>—Ô—nãQóöæÂŽ×{\†×°a³9pØ”õ)j HÑ´z{öÑç+·f×竎[—žŸýó5;ÿËçn,›Ã.¯>4"""""""""RhŽuŸ0iíû<}sM&]ÏþÓÌãÚw¥’ÂÕÔ‰3ùr¥¨ˆ%6…@DDDDDDDDDDDä2cmcîô_ٓ؃®å]§=»#ý;>›N£{ïå¦*A¾‚:ZRß®Á"šZ‹œ…±·)rA™¦‰ÛífÆ@ã‚}QÎ`ÅôIüZ²#½Z–=ÿwVý×ëûOä]ü4êmVèÅ3×WÐÝirQÐ&Ê•ƒðññÁf;½£·ùÅODDDDDD.,cåd&mÁ£7WçÝaɧ9÷Öy’ï¼ÛžkËÞ­ü¹h [Íb XäSXä,DF–Rä‚2M—ËÅÞ½ûŒsÍ³Š‘=žàǯ0õ‘x|­,¶¯ZÁÖÀ:4ˆ.†q¤œµß>ÿ„Y‰-¸§eÙs·þÿz}’•ÎêysYqu¼?þûóÇs¹¹¬…„„‰Óé<í°ˆˆˆˆˆˆÈéÀÔÛ¹zê‰Ó ó/¾üZ¾Ï½ø¹v,>îý9w`Îq©$Ãótÿ ðÀ–“ÌÂQ±pTÞûž°Ûx¹uig ¸È¤_©DD䜱²6ñØÜÕå:7¡AëNtï7‚/Öd`^™lÓ„«žá‡¬/ýÓ³$&ÞÃGÛL°#¢B#ó.–ž5ŒòIF-J?ë; §s¸¾³ªëþY<Ú²îŸÎÎÿj'þ=ög£ 8žËuˆˆˆˆˆˆˆˆHžŠÑTÞ¿žuÔþPäBÒ'PDDÎ +s)ÃúôåãhÛå.:Ç…cÏHaáWSyñž¹$ z-Ãÿãd›¨*1¸Ö±~‹—Öqö¼ÉÞuŒ}ô–Æ?ÉÈ;ªæ_ ½¤nHÁ\—¸HØ¢é:d4]/‹8Ädã—SYè¡ØÊ©|òg­éüvÛù‰ý¾‘Kœ»\3n,»‰¥«÷p dnî^…¬Yo°Ò£À"’À""rd³lô+LÚ^•‡G¿É±G:ÔmJëk[Q£ßݼüÚHšÕH«â{ùiÄ`>˜—ÌæÝäÚ‚(_ó*ºÞ{]kK|zv1ìF·œä=^Bª^Å=Jçªææ ‘1 Sض;ƒlˆ¨Ò„>Îÿjçø¯—>1Uˆ6~bÝ_±â‚1ùú·u¤îø‰u=ªRÃX‡IÞ°[L7b€™Âû=îàÛÄ÷™Ñ§y©c¿¿Õ‰ºo8iõò m `±{Önøb7»²|+ .§§€³Xß)c·c^gÌœU$ïÈÀåF‹¾2亰&³—1eÆ6êß;œfsbÔ' ¸³ÆÕ”8²QfÚ¿ïËPò # IDAT”ù»“ÅÞÜÇÒG0â³…¬ßçÁ?¢*·|‹^µ¤Îxš‡ßÿ•í™.ÁQÔïøÏöJ$âØÁt’8n?É:ö²d›Œœ±ˆ û BcéÒça›Ó8îDDDDDDDD.ŽÀ(ªw½“Ûûúcߗ̪™óø×‡ð(4"ö³©ˆˆÈYËYÆ—³wÞö)ºÅþm4UŸrt¼»ïšÆ—ó3iyí’—&±;º/÷Ã7g¿M˰“98ú=zUqÙ¬xçžø¾4={§"°dÜë¼öÄÊ|ú4Mü’’´’}•îå•§âpfoeÞG£ú¤“ŠÓž¦qÀ±ÕÛ"ªR=ÌËþ…§}=|ð’‰mhôÇ<~\×›Õàý‹?7x©Ð!./Áiü²Y¥û·Ä†ÀRǶկâÕÜwKÆnæMÉÐ'Gþ£.§§6gº¾‹]Î[À¶ò÷ðR¿Úyb 9I"Ö"}î ~ %¯¶©AÍÐŒ~n:ßíjN×ÒGJf_¦Ì¿ÉåÏ1òàd-ïyš>Õ‚pïÍ xY`Z«#¿ø?"Š[ìY:‰×ÞȰʟñÊÕG’²Çñøu¬zïšjЮÏ@‰¶økæûŒ|ì1rÞÿ€û«9Âw""""""""—cÝD†<6Q)b”‘³f¦¥’zØNlµÊœ,爩F_ë7ïÀ$/ùxEMäµ¼lÔ(ã¶{˜4i ·jFÀ¹|ôy‰ýߣw˼V»qýw²ð¦ùaå4i”·ÜbëÒ¸~5ì$P?|'‹ïúŽùë<4Ž?îò戥v5'Ÿ®[˳QÖzfÿ´Ÿ½ï¥™Ïí¼;g ÷W¯…}×:Ö¦R½fÅüÖ¾'çZ˜JåŽ%LMƒU›Ñ¶IÞöćncá³þQ—Ó‹Ó™­/1¦0±3¬TŸfõ«¼­æfÎXBHÛQÔ0p4ìȵ¡}˜þu 7÷Š=a¾SíËb§Q¦@ç3þÓMÄÜ9‘AÝÿ¹cÒ"ÿyõ*Aü5ç6¾Z•Š÷êG¿èœ<ŽÇXæ1á³T*ß9‘ºTÄÔ¯[žÃ)·1áãÜöòÕrLjˆˆˆˆˆˆˆˆÈbSDDä\°Î¦”³2‰ ¡^¿–-^ð¦nà¯ì,æ¾Ø+7¥~ã¦4¸é-’ÜY¤í=|Ò¥ØÊ”§¬‘IÆ¿eöð§fíXHùƒÕ‡,Ü«¿gNf®iXŠÆW'ñãlV¸,²×®"ÙV•ºU}ÎþâZ¦eNZ—³ŒS!Öw&±;oÊ,¾ZM‡ëâò’¨ÎtlÍæY3ùÃuŠÿ¶/ϸÌq<›×².;‚øør'IX»Ø6÷}úÞÕ•kZµ¤YûÇø$Õ‹Ëå:­zS×±>'oG¿ÙËS¯n8ÙÖ’ê-(öw""""""""""†šªˆˆÈY³…U B€—åk6àêPŸ¿w°ëIYKr®å£Ê`cÿI—aØ °ŽKOÚB¹öù·¸+îø”Ÿ€°'Y†a·cÇÄû<œÈ„zTô|IÒÚC„üô3Y‰}iX̆£–4|m8ßüÖ ÛïkðTîBB‰³ÉÕ°¼.§§”3_ßiÆîŸ\üñÍ,’s¶±¾s#Þ:a#öòÅÒÞÔmìŠúüm_ža™c .g¦L¦ß3Ÿbëø(/>Q•P¶ðÙÀçøù´÷šufûºÀãNDDDDDDDDDäÂP `‘KÌŽ¤)dn[¡@ÈË¿ZG6{<Ÿnü[ËKÏv¾þð+RƒšÐ¡I0'M¯šÛøý}øVŠ¡¼ìåcˆv¦³a‹I¹Š¹â裑§é²_QŸúa™,ûe"Ÿÿ-¯­O`5溦¿|9‰9Ë÷S®^=Ê´xÃ??8xðð¦ O3Ng¸¾s»ìå|õCµî~›©“&òé‘ÇÇ#¹§Ú~þf!™UêoûòtÊX¦UÀ6ÅRÉgIIÛø{CÜÜ¿Öò—U›Î½®£AÕJÄT©J… ã´÷›=ª*•}÷”´ýXïÐÞ­,_™†_lQv}ÌEDDDDDDDDäâ À"—ËdóœÁ„×íÆ½)”­q=6‡Sq‘ÿ@ ï}’[þx’á½{±¾ëM4¯†-=……_Må‹?ýi;àAZ—4òÇ^5Ù=w”oM­HØ4ûC>H.CLJç³|ݯŸÀýŸá){On¨‰3g7)™å¸áÚšÿ>fìßùTçªÄ`>ùb2;Ëtåƒ:¾GëÝ身 xdÓ¼¥¹õéJ‰k¯@Õ'“gO`JNÄZ;I/ÙŒ6ÕÏSœ¬3[Ÿqbwè×ïøé@uî½>¸ÒÇ'MеªÊ„ÑßòszK:b_Rˆ2F JYì\ö#‹¶–§IÙ“lSÇñÜ÷á“€ù©ý¿±än]€Q"ŠŒC¹–ªAd•–†z}¿Ô˜¦‰ËåbÍš5ÄÇÇ_$•NáýwðmâûÌèS õ¬{1€…Ø—Úßr‰KJJ¢Zµj8Nl¶Ó»ÎVoBñà0l6;†Í†ÍfËÿߎayÏ FÞkÆa÷ƒü»ò¾Ô†v†ˆˆˆˆˆÈ߬ùu¥Ê–U D䢶kûvª5hwZó¨°ÈEÄ2=Ú·‰ƒ»×spÏz,o.Å‚"I[ÿñ·}’WȰazˆ¿q˜ý[V(¸""""""""""""— %€Eа}[’(î——¼5-l>`÷Ãpøa8üÁQ L/9é›ÈÝŸLdLCö'ÿȦÅñØŠcsÏKÛl† »ë{·,UpEDDDDDDDDDDD.AêZ¤È²Ø·%‰ !XXv_°ûbøøcsa8±ùba‚eâN[Í¡ÃNüœЇE‘{8ÔÅcqW DÙ:ø^ ›ÓMî4rïÇ·XˆÂ|É.YŠˆˆˆˆˆˆˆˆˆˆˆ(,RTÚ»ÃrasøaØ|0œA¾%0ÎÎfÿ¦ßÈØü+ž}«±ù–ÄY›ÁÅ ð=º «ú5lÛ°Œ)‹q†Õ&$ЇᡸÞ­+(×R>¬ÝŒnEïïsÿö†/m?ØÊ·w…cœëuzþdÇŽhÅ^DDDDDDDDDDä2§°Hµ7u9%ü}°°±ÿö¦oeÿ®ÍdïÝ@1kvEß¾ýØ´i³ö–\VÒ6.Á—V­XÄìïf±zéÏìÙþÙvR²+²!«2Û\åÈ2Â0ñÁĎײc>X†Ëp`Zv¼€…ƒ\{‡²=¬X¿ |CHßµÓë.\e¼;˜ór7D•ÀÏ/˜Š þÇ«?íÄ{ä}s³žïDãªe(áçÄ·D]?Ú `îfÞ°;hŠ¿ÓI±Ð(j5ëˆ$ÏÉ×uªòæ6¦?ÜšøØÒûûàã_’ŠzðÖ’ ¬B×51¥(áïƒ(UÚöeÔûÏrKb,áÅü)^¦_[À~ë¸z¹S™9  bà !ºé¼»,ëT'ØÊ4jÒ„&G‰Ttœ"^^6¼ß‰ª¥‚ðóqR¼t-®nÛ½Çï‹]üüZw]Q¿@"+·à•%¹úÀˆˆˆˆˆˆˆˆˆÈ)uÀÌo¾âû¿=~~»ÝÑ2íޘħ=Ë}}툯Þô°‚'—•»î¸ý¢ß†"Ùز,æÌ™Ãøñp¹òO?ý7Þx#;wÆ0 }rÉËÍÊ$¼NsÂbL´ðby=€6? ‡/Øœ_ »Ãæv'†aÇ2lX¦Ë“CqOQž¼ž,pgáueaØìØ¿+ §‰©I6¿>-×4èöòD^­n±ú£çy¦C{²YÈÀú~`¥±ôëoØ3€ÞiBIO:öÊ‘ØÈféÀki÷ÆaÚ?÷_4ŒÄJJ¿{&óë/Ä;þ±®S–¯ÎŸóæ³»Æ`>~/¿ÃñÍkOñDç¾TY3†¶Å S×üeÔ~•©ÆãÜÿï<ö>Ï=/¾Ê¤Áìž9ˆ‡ž¹­exS'p˜O_K—O¢è7üsÞ.·Ÿ^~€Gn|‚ŠŽ¡]PA'3ÇÑT·aذÛm§ˆ—ÈÄ^¼úñã” ¶ØþÓ<òÜmô­“̤NÁd³tÐutîæ¦Æðb½rv¤Qì P4ù~¼Æ/%±÷ø‰^×ѧ©Ë–0ëAJ.k]oîÀ‡ã'\´ÛPäÀ™™™¼óÎ;¬X±€Ö­[c³ÙùþûÙL›6õë×óÀP²dÉB/Ó²,:v¼‘€¦L™rÂ{Ó§O§W¯Þ¬^½ŠÒ¥K먖¢Ã0±û•,L Ë“ƒéÎÁtÀ›“‹åuc™^,ÃÀÀ†ÇZ£Z&X¦åÓ›WÎtc™,Ó‹Oñ²¸Ý¹`YÿþùIÿŠ7F­£Ö3+ý@v EÓX®Žç¡ßðØ'7“—B¶Q¢F+®»ºþ±KæL†Ž\M徿1ñÉÚ8kO oÚ&p(LyƒâqWѶe}´ yÙÍüø1ß&¹iS«°u5(^¹1­®ªƒÆ”ÿk:3_«FÇ^¸Æ $¼I7°`ÁF¼Mã°íÿ‚a£·sÍ;syþæ0  î;©|;ˆi ަݵ¾'Ýœœ/{PҧDZnÕ§XüÇËÔ3 ˆP¢fn¨™÷¼^Ý’¬þ4ñKÖáéÔŸŒ¯ycäŸÔxf%cئ‰ËåbÀ"""""""""ò/ o6YYY.à§á5ŸŒdÂ$rÑ'‹Tø÷ßçwF‘ž¾ŸâѸï¾û¨_¿õë×ãwFñÇðøãOœðžÈ¥Èë5Á°ƒåÁ°ÙÁÇ›ÝÃéÅ–ŸÈµ¬#ÉÝ#Oþ4 Ë4Á´° Ëfæ'ˆ¼‡ÝÛUˆzxÖ/ç÷¬òÜpU%ìGϱ\Õ´,¿]ÆÏÍÔ/ Q¾gÝRV.GûÖÕpf]§YÀ^1†Š¶}ìÝoža]í”.WãPiY€p”¢l$ÌËÈëâÙ³a%«dó]åð¿ûÈ|&— ÿ°?é¸¾Îæƒ™óz›£cþ¥©ê¼mM.¿Ì“¯}Æâõ;Éò Á÷ G£Üë–±âpÞöiw9×½8‹{–¶çίNþ#¦é¬Hýî½¹£i4|v“ºðcÞ¿„õÙ6ÀN±„žô¹µ õ*à“¾™Ôù£8ñ/Ò-ÅV.>s¸HŒìr¹;vƒ&=}?5kÖä7^?!Á[»vmÞxãuâã8xð¯½6„‘#G’““sNëâõzyýõ7¨Zµ¡¡a4oÞœ¹sç;¹™&}ûö£fÍZ„……NÇŽ7òá‡ciÙ²%‘ÄÄÄ2|ø›Xǵ®t»Ý¼üòËÔªU›ððZ·¾†¤¤$}z¤@¦Ç †-¯•®ebð°þöúô¦cØðz½…¬‰Å™^›-¯/ã¼”0>80ñšg^W§†åÅkæÏm8qú˜f^âË[iþ7v+W®ÌüÁê50ææ ª­-ø êÖ«G½üGBõ²œêü³z(]»dKBÆ·€¹_½A§¨ãNÓ–‰¾'‰ˆˆˆˆˆˆˆˆÈ™²lN|}}ñóóËøâk/äÌF *÷BÿðÏyÿÑtyô#¾/×—{”ÀÖ‰‡úÖ$à›çxøŽžôü1Sï"S?jÊE¬ëÍ.Ê1‹DxРAÌšõíÑ×O=õ!!!ÿ(Ä“OöÇfË«ö¼yó0``¡×cš&999'<Ün÷ eÌСCy䑇ùüóÄÅÅѹs–/_žwr´,.\HÍš5ùâ‹Ï7n,ÉÉÉ<ÿüótéÒ…iÓ>¥sçÎ 8Å‹]0á# ÈÌ™3)Y2˜nÝþÇÁƒêK_ 8^½Þ¼1}ó¸yÝ:ÿ8Ù´¼V¿ÿœvbyða™ö/U¨í¿•ùs7k¸êIfÞ‚íÔN ò)š¢:*U%Ö¾…Å‹·`b›O·ü¹¬ë)—[‹j¾{ø=ÙKt\qG•)WÂ~ÎöyΪe¬6›pï€ÛiY¯5êÖ#6Äv\=jSÝw+óç¦WøØ—/_}hDä2ÿ ö É fñߺYFDDDDDD¤9µŸbìgÓøê³Oó“x§]áREžÐÜÚôwf~¸Õ.rÓ–1}Êr7l€·d9¢»H]»™‡“‘ú¿&:£ßzEŠ—ËuÑÕ¹Hô"º~ýz*TˆbÇŽíx< £àÖ†a`³å%¯¢¢*’’òW¡×3sæ·„……ÿcº¯o^â$##ƒÑ£ÇЯ_?z÷î @bb"kÖ¬eĈ‘L˜0þè<±±14i’wRKIÙÈðáÃéÙ³'N§“ 0uêT-ZLbb"éééŒ;–áÇѱcG† FíÚuX´hmڴѧGþÉôäu×|¤å®y’Ö½æ¶ÆÀëõªFÉëy¢OW¿| ½ѽºÅªžã•?«ñÈÛíóÇÔ-`Þˆ¹¯Ë@:½ØGŸãæ8ƒ3§ò‡ZÖÞϹëÊ»XÜáS~{«Åÿ¥üù¬ë)—Ö‘Gîz…v¯wåVÇ3ôl\ßÃ[ùs%îèшâûþ¶g¸߸šÄZ#3x"e»Ö ܱÍÇÝg„uäÑ»_¡Í‹¸Íz– ËàHßDFÙë©dºøy+‹í«V°5° ¢‹aè, r¹µ&sçv2œe‰ -ÄÀV»7¦°=6 t9 ÿéúürÒÎàîiO© \á¬E!huäÏqG áöTœ›gòéš—xü­÷‰_:Ÿ_¾ŸÉÌߣ°ËElâä)Lœ<墫w‘HO›6 €nݺ0½[·nG»©8qâÑDí¯¾ú Gá7¡I“& 4è„i?ÿü3¯½ö6$“}4± àp8HLLdöìÙ&§Ë–-ÃáÇÉÎÎÆétâp8ˆˆˆ 33€ää¿ÈÊÊâþûûЧÏGçs¹\ìÚµ[Ÿ9)¯éÃ(D«ß“·>UËÂòz ÙB*€†ƒfòeÀã<3¤;m÷@díkyê«aô»ÒÿÔ³¡tù ï<Áð·ð~º?±WVÀǰaÏ¿©Ì²Žë¶¹åÏ[]O)ˆ¯Ïaý8înxJ”§ÖͯrS÷Fÿûvœé ¹n_>z{ y”ëGdâuRª WV*™ŸÈ(ÎUCfóeh?^xÿ~nz. Ÿð8:¾Ò”‡j–(Ç­•µ™?Çä–²n{ngªÕ£M>ÜÝ(üÔÝNxÖ0þÉ'Yÿ¿)\©°ÈåÁ½ŸäåKIÚ°•=™Ùx플,O•„D”OgéÌ™¤Å÷ B¨óœŸÜ»óÉg¿ãLìBçø£ç'×¶yLú|%ZÜÂ5‚t.ÉgxrÉõ&1§ïK|¼ßçŸï»×óÓóÝIªÑŠv­šÑæé›éñûK<üêrRMý…-Ÿ‹5ù E$\¯×{t]Ë:ûÎüJ– æÊ+ëŸ0mëÖ-g½\§Ó‰eYy]ê’×JÙÇÇÓeëÞN;urØ›—`.lÊÒ^–ÖÏ|Bëg z¿&Ï¯Èæù“})(^›;GýÀ£ò·kíË4ˆGd„ #ìFÆmº±ÐåOºžÝù2»ûYÕÕ·ÓT²:_¦*O/Ëæéãçó)OÛg'ÓöÙ“,óïÛaDÒkv½8ÝxRçž1Ì»gLÁû§ž,öíÜJŽÇvNÏ®}›H>T’¸¨`ŽtÜïSêJ®©ŸÊÔ%?’uõBíàÚÆÂŸþ §âÕtª®ä¯ˆˆˆˆˆˆÈñ|¶n`§ ãý˜<Ç[@×Î9d¬þ†)«¿aÒç÷1tXGÚ”^ÂèíNP.*sòŠxø¿V¹r,~~~,X°àh¢Øãñ°xñbjÖ¬‰Ãá8Ú"ùtÄÄTÂét’’’B×®]hùW–eáµ<€­€îtû|üÃëÂ4=Xæù%ÑßÓßa±Y™˜²%±güÉ—¯½ÅŸWÜÁÈ:>ç ¼9¬ó2ãS+qß{#èUõX‹ë«Ûäu}{/Ýð(ëÿ7…ñÝËaÌŸpgç©üÖç<];ïøý­NÔ} ÀI«—`hk?…Wä’ãaû’9,M%ñæŽ4Œ8vŽ©R#ï‰w`²cÞx†ÍËûÚ{mo®u±iÉÏ,IÞÉÞ̼öbZtåº*^UÉJ]Ê[j̱³“Èz­h˜:Å?® b§Zä,þ™ßs£iÓ¢*AÊþŠˆˆˆˆˆˆàrÙ)Z§áÆuøGfÌîÆèÛúrçÞQLù㹡թ”̲”¼A ´ª—ÍÖß7²ùP1Âb" uíbáA›)•‹=ù JŸ 88˜Þ½{ñúë¯XŒªU«2yòdÖ®]ËСCÏx¹¡¡¡Ü~ûí¼ùæ[Øí5jÈáÃY¤§ï§[·n§óX.OGZ”[†­€Ö½Çµì5OÒ´y’n¢›fyó,7ͼñy;­L6/™Á°OV³eÏAÌÀ²Ôh~7“?xŽF~ç ¼9Ëø|ÖB®y‚ÛªžMwÛªtÂàö‘ذXÊW±¹y¶²jݪ4'!âT7øØˆHhOÛjÅ10ð-î2صq™Á i×¢ ~fFÉ ~=ìaÔkÕMS—ðÝw;qmr{ÍUÄê»™ˆˆˆˆˆˆÀ¯ß&qÓ=ãù4æU:¾ñ[Æ?LŸŒÞÜÛû&†Ùñ¦obýô¡,KÉÁ*FT“öô¸3’ˆ7ÙÛ’øyè8¾> T”\\.öä/ÑðßÇèѣDzîgŸ}ÿ† NZZ5kÖàÓO§R¯^ÂY-÷¥—ÊÇOä•W^!((ˆoìÈ-·Ü¢°œàøîÄuŸ6ϰÅïߦ›ù à3iÑ~ZŒP®{}.×½~žÊK‘a¦mfó!;1Õ+s¶)[¿Ð ÄT*‡î ¹„¯u‡ö³ßeοõïà(V’°ÐǺc¶ò®¾¡å‰.yì\ažY]l!µi]o-Ú„½bKšWÖä""""""ri:0õv®žzê2‹ŸkÇâã^üé9îÿéØkÃLcãŒÁô›q’¿áwÎfü ÙŒW¨E.¸"•Ž‹‹cõêÕ…*[‡£pÕ7 ƒ/¿üâ¤ïuêÔ‰NŽ þi·Ûéׯ/ýúõ=iy»Ý΢E O˜và 7°oß '”™?Þ e|||èß¿ýû÷ÓQ'§dY'ïnÃöÏÖ½–ù/Óþ9ýI`Ó}t=çµ°\NnÞ!«´‰ˆêœqô[Úy[…¹s>ã¦%‘q41¼“QoþØoØî Ãò’Çž}$oÜáãƒwûZÖî‹ËXDDDDDDDDä"U¤À/¼ð‚öˆ\ö,ËÂívç'k½…hÝûï-~ÿž¶¼nÜn7¦ib†’ÀrVláå)çïeå§á‹ IDAT†¸H8y+`Æݖ7¶ºˆ\ÞŒÀ`J8,v¤íÃC¹óò…ÔÏÝâð`qhí¾Ù]‰›Gã‹# d~Ëa»—Íá×ý‘4휈ëÇ,𳂍›ë®°ˆˆˆˆˆˆˆˆ\¤ÔæH’—´µp¹\X–¦§­{ Ùt~×Ò–éÂ4MrssënZä,øÕ£ÝU!ì™=©®®8Á„–´Ø™ºÜ“½oøâç>Ö8PD.M>别À¡õËX•^Àp†äæäžÞ9áHaG1BÂÉ'$Ð>Å '"<ŒbyÙ]Ïîe|¿,“Ò‰WS'¼õ[^IØ¾ßø!i/^í%¹Hiäm‘"Æëõ’““ƒišX¦§€Ä®ybë^ó$ÝA›'Içÿ*nyóZçääàõzq8j,gǤéýÒ6é^ïuëºv¤i•Ìl_ÿ;ÛÊuãá¶Q4»*šÑÞáÅÊn:ÄÁŽ¿È8’¬±W jŒ“ɳ'0¥F'b­¤—lF›šAêXZä’ãKtb3â¶Ïæ—iÓÙS§Ñø˜9d¦í ³D]šÆ•$"ÌAÒúe¬,U‹0åM\©‚ÎCþøûÁ­ÉlÎ&:Øyê*˜{Yþs‘ ¹µfpÞ]‘áuiŸÌ'Kae¥›Hѽ’""""""""rñQX¤ˆ1M·ÛËeâÊ܆£X–éÍKùßòæ%qN?î=Ó÷¿7oºiºÁëÓƒa™X˜¸³2ðz=X†C-€åœ±…·dð¸0ꌛÈç_Œà»´Ã˜Î "*V§iW&vªÜö"ƒ2ßàݱ/ðÝß PÊÔ¨G\¨Œ´~°/˼˨þ¿à)VžÆ½«sÀ"—$#0–¶]‹QférV­žÏúÃ.L»!‘D×öbáGå&-ØöýBÎLÁtsE£RT)0DÕuIùi5óVGS±Iéü®n ‚ã»ðPüñ…-®]IJ´`â»ÖæXž×NdBSj®û‚_%×¾ Å´«DDDDDDDDä"c´îÜ;¿í••ÿÏÊ#Ôâh²Xf~÷±–eaš^,ÓÌo¡hbš^fìeÙüïQ¹lÌ›÷ íÚµ?§Ë´,‹ÜÜ\öíÛÇèWï%.l^ ÞsÌRÓýéxïÛDEEáçç‡Í¦VN#Ó4q¹\¬Y³†øøxDDä?–””DµjÕp:§}-­ß„âÁaØlv › ›Í–ÿ¿Ã0òž60Œ¼×† à ï5ywÇäÝ"£ž>>Ølº·RDDDDDDDD.<%€EÎBdd)A.(Ó4q¹\ìÝ«°ˆÈ…JDD$N§S `)ô+•ˆˆˆˆˆˆˆˆˆˆˆˆˆÈ%B `‘K„À""""""""""""""—%€EDDDäâg$yÁ,~ø3KÑ‘˘C!‘¢ÇEæÎíd8Ëêü÷âV»7¦°=6 0@‘˜ÎòÔº¡;Ý[T¡Rd ά4v¥,çÇI£ù49¯½`«¡_Óiv;îÿÞ¡¬E.bJ‹ˆÈ9cemæÇÇ1ù‡¥¬ÛžÛB…jõhÓ£w7 W·"r"÷~’—/%iÃVödfãµP2²¥3g’߃ ¡Îó—Ð=U**‘,""""""—£$UîyW®øŠwßþ¡;<ÅKQ¶JÅxQ‡±"—%€ED䜰&1²ÏŒÛI‹oç™ZeðÏÝÇÆUËÈÌv(‰""'ÊÝ΂_ó[F 15ëѪt>ž,öíÜJŽÇvNÏ®}›H>T’¸¨`ì¨"""""""’7 9šg°xÀX¾]ãŸ71c?{¶®| ‘KŒÀ"EœeY˜¦yôq: ÃÀ0 ìvûÑç"çG+Æ¼ÌøÔJÜ÷ÞzUõ?úÎÕm:+fîeÉ„79cö„Æ&Ò¥ÏÃÜQ?,ïC3ŸF æƒyÉlÞA®-ˆò5¯¢ë½÷ѵV06r˜?°#nèÌ'ï"&o&þúàvº}Y‡w?œzº²‰\ÖŦ%?³$y'{3sðÚˆiÑ•ëªx%%+u)?n©AtT0þ§S‘K…͆Í(Fxx1l˜üK³ ÿZwðè5-iTѶ/fÖèw÷§¯³2zÜJ×Ñ\êƒ7m Ë>}‡·~ÚGvþÜ­_KýÙãØ~Õm´«ìaõ«=¼ÂÓY‘úÝ{sGÓh*øì&uáǼ3~ ë³ÕòXä|ÐÏä"Eœ×ëeçîm¤g¦Ã鎺`Øm6ÂCJR2ôh"XäœËYÆç³vrÍÜv\ò÷D¹¬zïšjЮÏ@‰¶økæûŒ|ì1rÞÿ€û«9$/Mbwt/^î‡oÎ~›>–a&spô{ôªâK†up~¿œå{{a+ƒ?þHÅ·öTÓUMäâàÙʪu¨Òœ„ˆSÝel#"¡=m«ÇÀÀ·¸È`ׯMd7¤]‹2ø™9%08tžê """"""rñ³ú™¯~éÌ›ŽàõÊ_ñÍóY˜’ëŸ% ÈeÍûñaZI*ty–!Ot&徉üâòàI[ÂÌwÆðç6“€„{èwßãôÜø8£6û`ÙÊÒâΖÌ7œç?Ì$s—%¨Ü{ý†2èÑ•$ûÔ¢õ#Oòr‡è4z‡vŽÈy ŸÊEŠ0˲p»ÝìKßKíê §¼µ,‹ƒ‡°mç¼^áa‘JËya¦mfó!;1Õ+ã[Ðñx`>K¥òy¡KEl@ýºå9œr>^Àm/_M`~ÙÀ+hÚ v Q£XŒÛîaÒ¤%Ü:¨õ›àxƒyK2èr}FîV¬µQ«O-üµ+D.ŽëÛ¡ýìw„E†ÿk'SŽb% -q¬;f ÀÀ7´<Ñå#Pdž¿:ˆˆˆˆˆˆˆ\üŒïgý{ñàæîôh};ôæ‘ÔEüðÙG|47GÛ¹Ù6g ³“X¤³ròL~¾¦ åÇ27y#Ë¿Úxl‘s&ñy»áÜk‡ÍÇæO<ˆ78ÒÊØŽ'¬·6ý™÷/du†°ŒéS–Óå±&0úSí‘ó@ `‘"ÎëõbY†aàråžöü{öî⊨JlNMÁ4-"#J) ,çá ¤…§1Ó›ºŽõ9´ˆ/w,ac/O½ºá¼·p-©Þ«©~²Ù•ILå£ekÙâmFÕ’iS/ÿ²˜ô×”¼‚•ÙUèÚ ¤Æë¹hÎGžœ¿O­¹s>ã¦%‘q41¼“QoþØoØ[«œÿ:ˆˆˆˆˆˆˆ%†g)ß cÀL?JTiFËkoâ–džR£Ø½Ü;3ë¤óØr2HÏñ%Ô‘÷ǼU¢* š5¦aµR„…R¶¬·Ã~Âýn— ë¸ô“§T®pÖ¢Æ ´:RÊH¸=U;Eä<)R àgŸ}–õëןô=n¿ývÚ´i£½&—•#cg”´ÍuåàïÀbؘšŒ…E©ˆÒJË9e /O9/+7lÄEB­€­3^¾a3ÀʟߦiÛFø¼8‡¹ûÛPkéRvG·$1Rã…ˆ\4pSÂa±#mÊ—/¤¶ðxn쇋CkçðÍîJÜØ<_ %±;3Ï{DDDDDDDŠ$+‡Ìuß3cÝ"’²Çña«0óç‚ }æ-ÞŒ;_½“†¿½ÇS>#y_ ê=7Š®ÿö;€'—\osú¾ÄÇûÕ—È¡HýZ^PòÀívóá‡òí·³´×Nç|+ÅPÞž÷9(ÑäZOâËY˘» •J-šQQù_‘‹‡O9â*phý2V¥{ øëÐrsrOïö‘#…Å '"<œ@ø',<œˆð0BŠÙ W‘KZ69XžÂý]ìª|5í_óÑÄÅ,ß’Á,…™ÓgëÖxêÐ0Þý„'òß(’¦M›öi;wƲ,ÆÀµ×¶+ôò,Ëâî»ïaúôéx½^üüü(_¾<Í›7çþûï#&&æ’ÝÁn·›îÝ{ðàƒPµjUñ)Ó:ÒRRRp»=ÿ~QõqP©R%öìIcÇöï %&º2mÚ@HÉ0l6›Z˹aÒôþGi›ô¯÷ºu];Ò´J涯ÿmåºñpÛ¦ÜÞ9Š»Ç=Ë €^´†ä™ïñáÆ+¸µ_“£ãÿ‚Éø |kjE¦ÙòAr:>ÔøX¿xnîP–“^fÛÁ+èö\”¾<Š\T|‰NlFÜöÙü2m:{êÔ :"3‡Ì´d–¨KÓ¸’D„9HZ¿Œ•¥jƲü£‰+UÐyÈ?8°5™ÍÁD;ÏAJ¨ƒh¹$˜ÅšÑõÎò”Ä©iìÍ-FHõèÙÞê-Ô2ìi»ÙÜ„¶ÍcÓÒ\œu»ÒáŠ\˜{êùl‡dÆìnŒ¾­/wîÅ”?‘Z:AÉ,KÉÑÎ9.ºÞîŽ$ívÛiu½wï^®¼òJ@nn.ý•ÂÇO¤qãÆ|ðÁ‡tèÐ^GƒÝãþ¸À•*Þ M5?ñCïpàñxðx<øúú*¸rÎØÂ[2x\uÆMäó/Fð]ÚaLg«Ó´« ?jÝ;œ·üÞb䄸/Bc¹kØ#ô¬~ü±hàë“É’ñ/ñÁNÅ£èòÒcÜŸàÂå«òM·Ð`Ê+üVýVÚE)ý+r±1ciÛµe–.gÕêù¬?ì´ûItm/~TnÒ‚mß/dáÌLg0W4*E•ÀATmP—”ŸV3ou4›”ο1Ä 8¾ ÅŸI4B°ˆˆˆˆˆˆ\,c[w'pM§G¹¾t(%홤o]Íâ÷ú1qAn¡–áÜ:™“ûÓïŽáŒ¹k/;VÎæçßëqտΙŖñÓ'£7÷ö~‡‰av¼é›X?}¨À"ç‰ÑºsïüÌ’•ÿÏ˲À²L¬¼'˜–‰ešùã‘z±LÓ4óÿ÷r0c/ËæwV•éܹ3Pp àãùøø0yòäÂØ,‹Žo$ ÀŸ)S¦îv»¹ãŽž,X°€_]B©R¥ðz½ 6œ±cDzgÏjÖ¬ÁÀ¹êªc§0¯×ˈ#ùàƒصkeÊ”áƒÆP·n]ªW¯Áƒ>ÀC=ÀæÍ©Ô«W3¦Ó¤Iú÷’ï¾ûŽ;wФI:tèÀäÉ“Xµj5AAAôéÓ‡GyøhËL·ÛÍ믿Î'ŸLeçÎÔ©S‡!C^%>>Ó4éßÿIfϞ͎;¨U«&¯¾:„+¯¬Ëå¢zõG×0aÂxnºé&ÆŸÀ°aCÙºuaaa 8€[o½UŸŠBš7ïÚµ;¿7X–Å¡C‡X½n%ê7ÅëõœüÏ¿·v8ÄÆþ3Yl·;X¼t>5âêxa[›[øüé~Ì®<ˆw]‡Õé‡Ð4q¹\¬Y³†øøøK`ƒRx¿Ç|›ø>3úT;õ1áú7ºõgû=“Ö6DI¹ ’’’¨V­N§›íônF©ß„âÁaØlv ›-¯g›-ïµaä=7l`y¯ †AÞkŒüìtÞÙO=zˆˆˆˆˆˆüÓš_gQªlYBD.j»¶o§Zƒv§5ÏEÓøø¤pçÎq»Ýg½Lž{îY5JäË/¿¤wïÞ <˜wß}PµjU&OžLçÎ]˜5ë[xõÕW9òmž~ú)âããÙµk+Vü×õY–ÅÂ… ©Y³&ï¾;Šôôtž|ò)žþyžþ9žþy¾ûn6¤Aƒ+ILL`À€|öÙg ò*eÊ”å7^§[·ÿ±lÙRX¸p!Õ«WcĈ·8|8‹áÇӣG–/_†Ó™×ýáÃ?Ì­·þ€òå˳fÍžxâ žzê)Z·nÅž={(W®œ>EEÔ±ñz bccÏáòÎ÷bžI¸ž­§‘4´9þ§U‘=,ýê têwzã;Êe,‡6rÈÊfõ§CùºXgÞk©ä¯ˆˆˆˆˆˆˆˆˆˆÈŽË=ÑÑÑ”(Q‚õë7‘‘ÁèÑcèׯ½{÷ 11‘5kÖ2bÄH&LOff&ï¾ûýúõ;ÚÊ÷—ËU¨uÆÆÆÐ¤IRR62|øpzöì‰Óé¤AƒL:•E‹“˜˜Hzz:cÇŽeøðatìØ€aÆQ»v-ZD«V­¨\¹2Í›7 L™Ò´lÙŠ•+Wrå•WAµjÕŽÖaß¾}X–EÓ¦M¨]»¶> EÜ‘.  Ã`ýúõx½ÞÓ^Fhh(‘‘‘',Q¶rª” V ^9ÿ¼©|óRÞÿËAÙº0äªú(,"""""ÿgï¾Ã£*Ö8ŽÏîf³é„€@¨¡½ Jé‚ÒD‘jo J³¡  T ({»¢¨¨)JWzï5„ôº{Îý#‘ E ðûú=öøÉýÒÓÓ9|øÈyË,Z´(6›'N\°ÞZµjÑ Aîºë.:tè@¯^½ÎÅLëTÆn™2e®jyWÌ^žG?ÿGu˜äj°•¤Ï¬Åô¹àùV†ÞÓ£·zJDDDDDDDDDDä¼l7{lß¾øøxÊ”)£íÿ ŸaØív22Ü9®ßétfÍ«lž,ÃËË 3+CÓ²,l6ï¿ÿË–-=ùZµj%wßÝö¼e:™qýÓË<›··7_|ñ9sæÌáÀƒÜvÛí¼õÖÛº"r©³3€7nÜxɯ-[¶œ<.9Ø<¢Qݨ‚Ó‰_HQ*ÞÖ‘q«Ýàù›W«øPvÀ ÜVk?ìEý’Á¸¼¼ ,Xû§ï&Ç!gÏAæëL­¢A¸\y(Vë>Þøõžœ¶GDDDDDDDDDDDä&vSg§§§3tèPiÓ¦ .— —ËÅï¿ÿNÍš5p»Ý,[¶Œ¨¨(%q:gló›ÍFhh([·n½jmü§¾;vЩS§sÖg7°a¸\.bccÏ»®Q£F4jÔˆ1qâDž|ò‰“óKîqúœ½eË–½ªåe/…¯´ ù[I´ò._×εçú÷šÍ{=pVâ¸gýhzb.…^šÁ‚f1l!.ü–>m’Â/¶à®ñ‡Íäòëg¼È Ö­HY°„Wj¸²oOU l """""""""""7¯›*RrìX4‹/&==;vðÑG³Ø²e3“&M¦@ôéÓ›‘#GâïïGdd$³gÏfÓ¦M¼ývfvlHHݺuãÍ7ßÀ²,jÖ¬AllÅŠ%**Š–-[2zôh*VŒ"**ŠÝ»÷\b°íL!!!<øàƒŒ3»ÝA:µIJJæÄ‰:wîœýv8¨P¡Ÿ}öÕ«×À²,BCC ËÇ¢E‹©P¡<lÞ¼™¼yób³ÙtUäBÿdž†Á¦M›² ü;NJ—.my97—·Ç¯§ôs2ó…J8ëèÆØfŸws3ú(ÑV^šÜÞÚUü€ª9ÿœ'¾å­÷6SqÐZ>x¼,v aýR$¬¯Ê[odz·'èÛ#"""""""""""r3¹iÀ!!!,X°€-ZâííMxx8 6dÊ”ÉDDDœÜnðàÁøøø2jÔhŽ;FTT>ýôªW¯vr›¡C_%88˜)S¦0tèPBCC6ìu¢¢¢xæ™§‰‰‰aĈ‘ÄÆÆ’'OªW¯NXXØe·ýõ×_#$$„>šÉðáà äî»Ûrï½÷f»¯a ú*}úô¡K—.0dÈ`¢¢¢3f »víÂËˋʕ+óþûï>Zr—Ó"ˆŒŒ¼ªåeǽyk’ ÓªI9r’îU·ýE¿;£XÙ¹7<Úƒ»«äËÑÍÆ½eë’Ãis{Iì'ïR¥¸½~!^ù~%[Ýí©r‰í¹™vò}É’%™?þ9û­Y³ZWÀuâ’2v¯ry–LJ‘³¼£xbîšÏŸÁ»£FÓ½Æ[Œ:ŸTÇ'ûÚ°®v{n" T'ˆˆ\c‡V'ˆˆˆˆˆˆˆˆH®’+À:tБÉ-7‰’‘”²¿Ç²e{1k•ÈÙ\¾†?Met“®´ïW“FïLfißê4¾@Ê®ef†}eªQÉg<‹îÄS»Lf°{‹~?€o¥j”v\f{DDDDDDDDDDDDn¹*\¶lY6oÞœ£m¯Æ0¸"×£œÌ|6Ã0([¶,v»ý’ë3Âîæ‘Ž¯ÐnhžñBû²;ç~Â_nhxží=;¾câ“ ÃñKßÇ‚' 8”`XÑ_Ñ£f–µþ”?ÇÞA€-„|Á{~ûŒ·•¢E©»è÷XY »—>þ¯Ò¥¼Åß3†0|C9ž~§A—Ø‘›I® :TGD$×üá#„Öã¿c¢o?F¿|/OøPªf¼ öÓÒoÿ9ãÈ_|9r Ïí8NšW^ŠWkͨ)ý¨œu·±¬Ó†y¶ãþ!Ïò¿G>à¹Ûpçˆ:Ô~u.ßøöeЛ]hvòWjÁ€oGÑ¿¦Ï%µGDDDDDDDDDDDäfäPˆ\_.'ø¸\®KÞϨD÷÷~¦û{™ï=›†Q«êTò‡ÙÀL >ÁÂÇÏpÕÈüÍÏ_PèÝLÝu÷é%Öt8¿î~j‘½M}L“A—Ù‘›˜À"×™k?ü¹› _¼Ë2³4…òbÝÀ7#Ʋ¡ø} `%ó'ç˽¡4©YûÚžnŒ¯ì¥DDDDDDDDDDDDnj ‹\G,ËbÓ¦M˜¦™ã}ìvû•­8v/ÿ’Q¯gïÑLÿBThГÙ°¨E#Þ §Aÿɼr‡ï5ê„ ´gÒê¸tŽˆˆˆˆˆˆˆˆˆˆˆÈÍM`‘ëˆa”+WîWBË‘ i9òÜUwïLaô5ï„ ·G䂬dü½†}þ•©UÂïäœÕ"""""""""""7€E®3—2°%K–T§É5‘öÛ`XO‹±³\ËוּvNïͽ<õÅûÜË5ž¯Ù½‘i/¼À–ûæPS`¹),r¹ös‹ä„IÌÑã¤{óÍ»ŸÐ©ZwJö cEÿÄø™I3‹}‚[Ôc"""""""""""ÿ›º@DD®œELôq §ÀžÏ˜´0ëäºtÖ<%E(b%&ö´9¬Íh–OÌý­QãÖÆ4ë6„)+¢ÉÜ"•ů6£úý“ÙfžªçØS³ñ‹ü𠏳øƒ›ucÛQ¥F-*Õ¨OߟSsБë‹À"¹œaØlW÷RµÙl†Á•«É$6&[D'ú¶õgÁô/Ù‘5R¹u|>“¿Šãöž=©”BLLVà•4þžð4ONÛCįðÞØ—x°øN&=û,6¦.ªÔ­Š÷:öO86‰Õ+6a¯T›ª>)¬y÷iú}“­`Êû/Ñ>`#úcIÒùÚè L—Ñ|öñl¾øxÏÕõÎADDDDDDDDDDD®/Z$—³Ùl„çcþoó°,ëŠË3 ƒÐà0ìv»:W®¢tbã’q†Qç¾û¨òífýщ—êz³í«YÚ†©‹°`l>‹IFü"¦¾‡ÒÝgòRÇbØ€UÂIÚÑ•éýN×að«VjŽüþGîÊ‹‘²†Å«=Tz¸ ™ñÕ1ê>?>ó`eŸ?Ä’{&óóÚ~Ô»ÕyN+]!Eˆ(YøäÓOVÚ௃+""""""""""×€Er1Ã0p:”(AÁ…q»ÝW~Ñ;¸\.¼¼¼ƒ»ªe IDAT”,W™@\<ø‡ùaÏ_ƒ›N£ï¬yôŽÊϬ/RóáD:SYé qñX€¹g3[RÃhXõT@{8Õ«äcÂ’Mìñ4¢|ž:ÜYÝÆ°EˉkÝ×ÚÅ,K«Ä£·…bíÙÊö”d mMÍ×N6w† ïè$,œdw†{rÒ=+!""""""""""×€Er9»ÝŽŸŸ.—ëªeÛíö«>¬´Üì’HH_? |¨uo;Jtý„qcò²Ð«)cî ÅfÇÏ×"1! 0ÈÁùlä¡Þ5±ÿ…Å'üËR«=Bƒ¶Z¼8–eOÒÚð "g7X:t""""""""""rC¹ªàýû÷«GE®²¶²Yr53‘„Dpùø`¶¢­¹¯öLýï å@u€>.HÊ ;‹FRÚû3V¯>€U43׳Uká*U–¢vƒ<õ[Ò`ô‹Ìý~.Á‹=Ôë;ÁA ç'lÝkR¸YI¼.z!yãrABBÒ!_{ŽÚ """"""""""rýP  ˆˆ\9+…¤Tðñqe¾7òÒ¸û£tlÓ“ÇÚÉú²qàããÀLJ$Å#°>v(ÊÖ©ƒyõ³Å¬\µ˜9¯fòÎâtîRïÔÜ»¾µhÛ$/+&ŽçgCÚÞ€yn§Ë]…Ù5s&ÿÀâ•«ùã÷˜=÷o’ÎNìµ!2ÂÉÞ§3gñ*þXôóþއµÁäÈ÷¸³qÆ­KÏ*ÐÃÖ½iÐô æì1³–%±tdê·}_c•Y,""""""""""ÿ -""WÎJ!9\>®“C/{—½›ƒNßÈÀÇÇ…u(D ò.*><š±®±ŒŸþœ€Rué1êi*ï}Ú~NªÜÝšÒ_L"£ÕÝÔtý³ÜOcLžw˜ð¿Qôý0 üóSºñc4n…ßéc@A4yâ9V½ü>ï=¿·_8·ö)ÏQÅsØ8g¸hË:wi‹«2T»ˆˆˆˆˆˆˆˆˆˆÈå2štè“õKµ•õ?+óGm ,ËÌü!Û²0-ËÌ|oš,ÓÄ4ͬ=$ÄFóõœIêQ¹iìܹæÍ[©#ä?eš&ééélܸ‘ªU«ªCDD®±Õ«WS®\9œN'6Û¥ ®YµyB±Ùì66›-ë_;†adþ·aÃÈ|oØ0 2ßcùÄMæÓ.†aè`ˆˆˆˆˆˆœeã?P P!u„ˆ\×8@¹ZÍ/i -"""""""""""""rƒPXDDDDDDDDDDDDDä¡9€E®À‘#‡Õ òŸ2M“ŒŒ bbŽ«3DDþ11Ç9zô^^^—<´ˆˆˆˆˆˆˆˆÈ¿A`‘+?u‚ü§þ™8:Z`‘ÿBppaaù/k`‘ƒ~¥¹A(,"""""""""""""rƒPXDDDDDDDDDDDDDä‘ëÀÇ¿É+¯Å4ͯ÷­£,þ`(£~ØÏ¿úé¬ã¬þr2ÿƒ¥s^DDDDDDDDDDDD䆕û3€¸÷Íl^º„¿¥ü»YÏ!–}þ9 w&),""""""""""""rsäöxáù+/ÄŠç·a=yó·ÒL°;ýÈ“¿(e«Ö§e»ÖÔ)ìÒ™ """""""""""""×½\>üMÒ3Ò2x6Ûå&,{HŒ‹ÇS¾ #zVÇ™‘HÌþÍ,Ÿ÷1CzÏ£ÅÀ7yº^¨&D‘ëZ®_Í! Àp*DUÀ jm¶hFÕמdÄ[㉊|™&!˜ÇY5ç}&ýï¶Ÿ0.Y‹6½æÞ*!ØHgŨ. ÜЂ÷?èF„ÈXÁÈ{_ä`×™ŒjŠEܼÁtzÇÉó?Š}Úf.ßÍÁcq¤âChDm:<öíÊùŸÿ£]´~“}ÿ{•AÓVq(.GP8UZöæÙn5 ý'‚¾Ÿß&¿ËôŸ×q ÙEÈ’8,œÿÔa%³ýû÷;{1›¤à•§0u{½Î ¦nè·oWÏÁ/¬,A…«¨3DDDDDDDDDDDDn27ÇÐüôhòP[¾è9ƒ¹‹¢¹ãî@6OÀÀ/ ÷@ïb»~šÆ¤ƒH=އÊ:‰¬\çO›Ùo‘×À³w›â2ˆÞ¸•´6¡¸È`ëú-Xe»å“ÈÜus¢xw?[gê–}<‰÷_žH‘i}©é}vƒÒØ”Mý`\¾%}v Ôß"zͧŒŸòïGÌ`ðmþV+ÞÀ°_ü¸³û@ž(êàØúyÌÚt*lîú‚7ÆýAèƒ/0¶Fæ‰}$……(ø{#°LvÏ|U:½ƒBîÂæpª_DDDDDDDDDDDDn7ÉÐf+TŠRþ&î=€;a½—]> ßÝE°U*&yW>ùl9‡Ü†_¥ª”e2ë6¤Òªž‹ãýÍa_Œ ±Ý]— Æ.ÖüD±¦• 6<€O‘JÔ¬Z;•©z˜•Ïgù675+œÙ+aYöõcàW¢·fíS¦T;<̼{ñÜV{ü¾ú)š²ÝGÐïî[2ƒºýÙ1ïÖgícÆ Îò§z¥Ê”+åJéJ¸Q6ÜIG(P¬*ÉñÇØ¶p…«´Ç/¸˜úFn>V2þ^Ã>ÿÊÔ*ᧇ\DDDDDDDDDD䦫¦½8hƒ‡¼È’¥KO-<ëû•«V1äÅ—8hn·ûêTleþãÙ·•iaT¬TðTÇØ Q¹b(©Û·°ÏFpuj—Ncݪ-dX‰¬[³›²;S9~-kö™˜‡Ö²öH!jÕ,tÞε(D#ž¸xëœu9©28¸d*/?ñÚÞÅ]òõ>é™eìßÉw>Ê—»`°ÃQ¡5«&òÅs=xæ­9,Ø‹G×Âuɖȉ}kˆÝ·êÔ%cX–[ìFB‚|9ô×WÞü3–eªÃä_g%ïfþ/ѽC êÖ­K­h÷èË|°ì×ü todÚ /ðÞÒX:4""""""""""r“ÈUÀÅŠe×®]|÷Ý\,Ó¢^½[ÏzÅÊ•|õÕ7X–É-nÁá¸òæ›û6³5ÉF¡"…°ce$0òS³vq>üá¶&&ñ熂ÔìÕßµ1Õaš8þdgþšiÇÙù÷JâRÊÀ¹rUp׺P¸Paæ~ÿ= ,<¹nåªU'ƒ¿¡¡¡t{èÁ+¯Ð}§|ËN¿Z´¼-Gxi"¼ò׺C§2Õ<X÷w4Þ%KnÏì²ðzõ(~t)ó¿XÄš¼Õ¨^0”êµJ°cÉ|·x a·ÞF©K‰M[™ÁZ{êOßµ…]VmºÝIÕ2Å)^ª …OÆÌ2ްfÕ^2.V§áC¡êmxü͉Œl—‡Í_ýÀ·.ˆÜ(#5Žc;³cɬÿáŽmþ‡™BbU)Ù˜à"Õp';íØÚ1­Ì£ožä£³ï´Ãl[8ž˜½+Õ©ò/He͇ض§$¼7…QOv¢eƒú4jÚ–žý^£o£¼™`3šåSsëFÔ¸µ1ͺ aÊŠèS÷<ó¿ŽyŠûîiAÝ[ëR­~3Ú>:œ9Åfm“ÊâWšQýþÉl?µÛ'=@Öo³òœû˜›ucÛQ¥F-*Õ¨OߟS³vɦ"""""""""""ש\•ìr¹èÑã!&OžÊþûùñ§ŸððçË/¿>üíÕ«'—\¾·‡uk×át'sbÿ&–ϛ˯{óÐrà410¨K§¶ExfÖk¼íÓ&Å,vþ4•Y»‹Òþ©Úøe•c+|;JÍ`Òœcî4–âv[ÝzŸ<‰O¬pî}¸TÎ:ÖDž@‹Ãk²bajξ~Oxq [_óÝÌŸ mXœ<öh'Êö5êroÛb<3ûe^¥+­+†á•²})§¶1.çÛµ&ÅK„áã>Êš=‰D€ÒórËô°uÁx‚ü|±ã¢`‘(^ÞX–…'%†Œ¤h\¡¥II8Ì¢ÇÛéÀ²Àt§ež†ð' Oü\†‹}«?Á'¨ >AÕÁrõ¤®ä«|g?ºFú\`£4þžð4O~bÐü±Wxº„Åö¹ÿ쳤NœÄ£åœ@<ÛV¬æH‰Þ {¾,Þ©ùó‹)Œzb L wo*×®Œó§U¬Š~ˆˆ0X±üõ×¼+u§œãܯ¹2]ÞäµVù±aÿ€wÛ!""""""""""r}rä¶¹\.zèA&MžÂ¡C‡øñ§Ÿ²æ4µÈš^½zpÉÁ_;þAØVÏfÀ³³±yù’§@QÊV½—×·¦NaWÖvÞ”{h¯{¿Ï¤9Ãø)‚KÖâþסsÙÓG¶¤q³JLÝr‚Û”ÄP°> ÊLf‹çNš–´ç¬YF~îx KGÇûso¥zŸrÙÖo/Õ‰OgÜœ÷øe¦Ã‡€àp" úg ¯êMäCoðfÐ$¦|óCf$`:ÉW¸"õ‹úa'v²è“/˜p ŽtG·”®Ã£ý;aבÛ$Ûžt|\yÁ²°RcÈÈð» ìÞ`@z!Ê‘7O ‡£W‘xp=ÁEpÇïÃ030 ;àÆ–‘€ËEÌÞ5ŠRX®óØnv'Ú‰(_ú‚CÉ[ñ‹˜þùJwŸÉK‹ajT 'iGW¦ô;]‡5Â?k[ÿâÕ¨_«v NR]{1kÖrîõ6ükÔ£šã--¥ã]ÁiY³ÉFÅÇ*r¾Ð³+¤% ŸòâRÚ!""""""""""r½qäÆFùúúÒ»WÏ“™À–eJÏË þF }JÃA9ØÖJõ.C¨Þå¢ÚêMæµ:}QÚGû3Ê*΃þÀƒUûÝÁë?ÞqòmÞ==»ç%ÔïCD˾ŒkÙ÷"Ÿ!/•Ú?ÇØöç_í,£¦ß§³ÿ:p|ïj\vÀÀ´<vØ]™ß¬ °…¦‡Ô»H3}ÉQ›#›á¸irKTkìN;¦ç†Í†aذ§Ç½w…¢Zªƒ¯+šß'¼Å/ùz3¸} nÚç(¬Ì9Ì‹ÌôëÙ³™-©a4¬z*‹=œêUò1aÉ&öxQþ|»;KS·Z3Vnb¯ç6"óÞJÓ£¶`'Z·$pÛÖ¦”¡S­¼9šg8GíÐ1""""""""""rräÖ†ý“ ¾›w^œù+”ÇßS¡7«üìߺ’C;–á ­D°¿ ‡á&À‘Îñ}k(X¶±:úB2V0²C;Ö>»F K&WušßÌO?NçËNQt.zî<ºö¢‘”öþŒÕ«`FÍ̾õìcÕÚc¸J•¥¨8ßw·¹ŸuÇ»dávƒ zmhðßü°’¿ï¡dÃÛ(vvü×ðÆå‚„„¤3B¾9j‡ˆˆˆˆˆˆˆˆˆˆ\PrbI‰‰ä ¡@Á‚xyyýkuY–ÅÁ8qì&üg¿×[€ÇcrüØ1ò…åÇéòεÇÇ¡ST$wŠÞ³Š /,lÄÄÄ}b1‡w“½?ë(vÜ$›~àŒ_žø&ÆíðÁí93ðæíŒ‘áàÄáìó¸ÈŸ/”BÁéÛ±L`¹z ê?ú ÍV¿ÄÈÞ°¹S[ê— Ã׌çÀ–uì/Ü™§šÕçÁEé9u0¯úö¦U Ø6w“wçþþõN›w×äÈÂYL oBÅü°ëÇÉLÚV¶OÞzjWUÚ·.ij†±?¡8‡åœü_{"#œÌþq:s*´£”uˆyo£iTNÚaräûA<ðö>ZšÂ“•œ€‡­3¡÷,oú|0–ÎEm@KGvãù%•xeÚ åÑ£"""""""""rãKIL$4, ’““Áøwõõ÷Çn·süÈl6Ûö¹½ìvÜnñqq„ºÂríñ±éÉŽí\Ž7©ü½f)?Îûõ+~ãèíĦØÙ‘RŒ­É¥ÙŸ^˜d#/Lìx,;–á…e8° ¦eÇX8H³“˜âfÍ–ýà̉Ã[1=9kŒy„E£ºQ?"§¿¢T¼­#ãV»ÁJ`퇽¨_2——7+pÿôݧ9=‡ùmDêϋ˟| º4-kÝAæëL­¢A¸\y(Vë>Þøõž“õîç‹§šPµÔ-äññÂË'/Åê<ÀØå±™Ù­ÈØÃÜ—;R§T>ü}ƒ)Q¿;﯌;µþbí#ƒeý"°†áCûO’/ÔAìŸõeóùá}Émð°ub;" âòrpKEîòþéó_õmFÒ·äòÂé—ŸÈÆ½ygi4f.;gmùóÚÔwyáμlýzƒú=ÃSƒßfÚâC¤™é˜¸¨øðhÆv-ÊÖé/ñÈ“/1}gqzŒÅ#åORÊÀÛ+ŽåÓ^çɧ_bÒÆ|t|}OW;}H ¥ï¹—Z)Lj/ßœæEÏóufÑä‰çh²÷ž‚'†ÎäÇ-DZrÜ8g¸è¬¹ŽÏÞä_ÞDDDDDDDDDD$±,§¯/–eá1M<Ï¿ú², §Ë…Øíöÿôåíí$ÃíÎÕÇGÀ"¹TZrù*7 4¤VC–Ç `sa8¼ÁæÄpxcØ6/°;1 ;–aÃÀÀ23°Ü©¸S)êNÅãN†Œd<éÉv'c–ãNOÆé”MKRXñJ š¿•D«!ïòuíüX{>¡¯Ùü±×ƒÇk4=1—B/Í`A³‚˜G¶~KÖÓ%)¬xµ%­GgpÏK2´z0i‡¢ÉS Há[p×xƒÎÃfòFy‹õ3^dPëV¤,XÂ+5\``âÅ©ðM¨†+i;ß@¿ÏQfã‡4óÍf}@¿lAÇ‹ÒôW¼S8†Ÿ‡=ÎÓw÷£Ø†ix±öxQ¹ßWL06‚Š\xLß²íy婆4ö2÷Íþ—ÐùëöæúR(Å_ßâé!]y®ò6fµËƒaÅð÷¯ 8PîUf¾[ Wò.~›0”~M×»h+yåªóÖ\‰Ž}+ѱï…6ÈGî¯Q§ûÅJ1ÈS÷Q&=VŽ‹Æl *J± ¼înÂ-xœÉ+¼C&7cÈ%·ÃFþÃù©ÅéËì”~ðC>xú2?êöÿŒßuË‘\.°Ótæ</À²2H=¾½»7³ú›™ÌZ“Dú”ûqçc,ø #6žù‹^ríaüÚù7Z=õsŽÊ²9}q‘BrºuÅŸ¡ù[³xhC:N=pÅõ^Š;Þþí~lΣ?9¸ÑÓFŒó%Ì\ƒ:öÿ~?Ë4sõ±QX$×Þ9Mì® ÀÂt§c¹S13R13âñ¤¦ay2°L–a``Ãâ´\EËË´<`z2·33°L7–éÁ+ i“¬Å¸¹¼=~=¥Ÿû“™/T XGw0Æ63ú(ÑV^šÜÞÚUü€ª§öýoß@…Ak™Ò·ì7ëÄ·¼õÞf*ZË—Å4¬_Š„õUyëíïxöãöd†¦ ÊÞN³Æ5pÐ…vósÝø~uÍê]|}Ó¨¯õÁî|w!/¶Ū¼»‡y¥^å³ßß¡yÝ ·ïŸ¿| ”¦B…’Ù —`#¸Z:µ¬¨_`óêÌÌYZxÕ”6Q™%U¯’—õŸVcÚò͸ÛÕÆ+«#Ò¢Ifùw6­„QãVÆŽšÇÓ[ã}S]©ܺ“D+…õŸ¾Íÿü:0¡q°æhÉ×_#èþúj¢ n)FÉíé>ø|;€§fì%ör£yfnïÕœïûÏc}Æå>ë¤xŸO•Ú“ö!ã ?Þ•ËY¼/áªÕ+ç²,+gq†«Ì0ô‹pv4´H.åñ˜`d>ÅbØì^.l®ì~ypø‡àȇ#0¿¼Ø|ü±yû`óòÂpØÀn`Ù,0,,ò™X6°lFæËî…;##GOæ¸7¯`MRa5)‡ó<ë½êö¡ãXÆÞE£^oðÙšcü3ðAÆæ•¬I §þí%ÏyÚĽeë’3×|VÇQŠÛë"iíJ¶^`ô{±ŠÙŽcf»Þ½u-'%ð]Âø¸\¸\.|Kõå÷ôŠ'ý"í»öbÍa,ÒØùÍ:ÞIxhBŠ´âÝ­nÒRS/òWZeš6(@üš•lsßlƾ{ý1:wiGjóò›ÝˆôÒýBDDDDDDDD$' O ÉÉÉ$%çð¶U,™=Þ¯m$µm?ŽL¿ìr½WÍâCO/žið¯gæô3lüx<ã—Äë ÿ[²¦Æ»æ/õ|Ž(X$—2Ý0l`Z`™Xg¼¬³Þ_Úr 'g÷pŽ sÙ™Û† óŸeÙÝ$JFRÊþË–íŬUâüÃþD4y”ÑMºÒ¾_M½3™¥}«Ó¨T%Ê{¿Ãâ…;p×>sˆeG™jTòÏâ…;ñÔ.“™ìÞÆ¢ßà[©¥€çÊúÐQª"å¼Ç±n›‡÷W8'ƒÙºHû0|ðõ¸q˜\þp Ùµ!éû•¬7ëñþËÒ8Ø3RÁÙÔæÙÁÒe‡ð)_‘’ ]׋ˆˆˆˆˆˆˆˆˆ\&ë(›·œÀU68øS¬ûÞŠüŠñÃǰ"½45êË /»‰{ê+V¥Ÿÿ÷Kû¶ÉŒ\ø!ïu¯Âoo¬çÈ9??gSî¤ûyÒç F¥ö¹ô¡˜Ïù gr‡¶ãÉç¢ðþ`OýCJHIÊ8gÁ‰Ë¬×²¢a÷Æü0u4/NŽ#îð?AoùòeæÈG“Rœ*]úÑÿUƒÄǧ³ Ù~c:¹(ìããÃÔ©SÙ¸q#½{÷&$$„0jÔ(zöìyr»>}ú°{÷n :ïòëÀ"¹•éÆÂ8•¹kž'»×¼ÌL` <žœl„ÝÍ#_¡ÝÐ.<ã?„öe vÎý„¿ÜÐðìøŽ‰ L*T Ç/} 6ž€àP‚m`„¶å™žÃi:´]­Á6Ä”¤Ûu¼Xû*–¦jEãæ¼Á;µ!ÊÚñ|wѱÔBzÖìÁ²ÖŸòçØ;¸Â6ø–¢”5ž_›I¡NÈç8À¿4=ìÿö-†•º—Úá°yΫ [Wœî#Z¤ÿ–WîêÅ7ûãt݈ˆˆˆˆˆˆˆˆÈ%2IJHÆãôÀpífÁøy·7°‚ÆM#âý¹¯Ög¬Zì¼@9Iìž3‘¯G?Í“5z2øÏ3×f_îÕû góä-LQÇaþØ´›C‰vHü‹?®¸ß2Ø3ûUÆül’9`¥ýÔòŸ>á—],Öòë;(òî³tªÿ ~¼‘Î Ë4sMkî¸ã‚‚‚8p )))¼üòËL™2…ˆˆÒÒÒ2Ï·;3>’’’r2|öòëæÉ¥<¦ #Y¿çϾXËÂòxr6³$†Âº IDATBëñß1±“¿¾|/Mšvã­åqx6ì6È8ò_ŽìIóºÕ©Õ¤;Óš1jJ?*;¸ýÍùf@EvN|”{šÜÉ=Žâë 1˜øRûÕ¹|Ó¿ ½Ù…fÍà­ åðí\^ªés•z1†#âëUÙ?õiÚßy­»½Èì¥ûˆ·²iŸBû7Þ¥{?x©ý´îñ&Ÿ®=Læ¨Î—2|õÅÛà¨ò3Þi‡ýÛg¸ëÖšÔ¬ÿ³ãËP¹dÞÓF 6p93xOîjÙ…a+ ñÈœ¹¼ÙÀÿÔW­¥™DDDDDDDDDärØðó÷Å €»p)ÊY³qÏ©aœí‰²r{^ ‡‡\´${â"¦O;Bñî©ë{æWRî¥~†³9wÏåÓÕh?v"ãŸëL»J¸®¸ß,2ÒÓ³ý­Ø–ºŠU[})T$ÿ wæüs_H‘"E8|øðÁÛM›6P¨P¡“Ë&MšÄ/¿üÂ/¿üBddd¶Ë¯WÊÉ¥23tf÷ž»ìâÁaOf€9‡aL# Ýßû™îïeµmÓ0jUJþ0®Ú™¿yà…wö §é 94t¾¿ ÑdÐÇ4t¡¿¢xqM /ž¾,¨ ߤtÉz“ÝúÌú› žM³Á—Þ>g©û™°ä~&œ±´SwÝ} mÌ® þTîõ!‹z}xÑ?^B› ç×á5ιi›€r/ÿoÔ…#"""""""""—ÆÈGÙ²^·ËnÇn÷Âqút¿–› 7x<Ùe|Z¸—¾ÃÛ Çñ\‡_Y²å´5WTî¥}†sVglá×»°ºÂ4¿ã6šlÏë^ç©7V±ç‚UW¡ƒ=ddx2§e¼Áü'‰I¨óСC,X???’’’(^¼8{÷î=¹ÝÝwßÍöí§Î“ðððó.¿Þ]ÕpáÂ…u£”›ÆÎÿÞÀ²,<–°] €{Ã>Ÿþò¤cšn,3'7g7¾x—efi" åÅ»oFŒeCñnŒ¯ì¥!)X° :ADä;|ø°:ADDDDDD®cFÅ®t)þߎ‰¼pÚË[=*—qóÝ_™Ã=›Îò”+Í®Cv¹³Ö!þøðK6Œ|šn駦"̾Ü@l†qÅŸáüR‰]ÿsÖǬ¯áíQmizËr>8à}Þz3<þ\~¿žìG"K¤²çËCÜhy™¹idÊùóçóä“OòÆo0nÜ8|}}ð‹Bz2ûÛW¨ãÒ±¹^X6'ÞÞÞ¸\„­Hõö/3á¹üyÿ=>;–8uÄÌåãEù¹õá>´)@@ÞRÔìÑ“Ž‰3ùd­3Gõ8Ž|Ƹ¯òÑ¢]$>Y±ÐìËõpüØ ¼ËÕ¡Z°ooÇe†³y«Ñ°Q9"B\8¼C(‘ŸôÃM°]°Þ¿7yS¡MGj‡ùàV‘†wD’/GQ5'%îêFËRAä)Iå®ÓÍû3>_~ã…ä,Ó¼æ¯ ‰§Gxyy1cÆ FŽÉš5kèß¿ÿMy­kh‘ÜvÃÌ þf2ÎÌâ5/3ã÷¬åfVØãñdß #„–#Òr¤ŽÍâ|CL‹ˆˆˆˆˆˆˆˆˆ\†ÔJ˜ò9àI&îÐnvm_ÁÏÉONK²âØòA^îÚ‡O¢‡ý{WÍâå!óù+#§AL7±ßcúm#ègÏi¹Žÿ0Ê÷ṉ÷`.F‡q›.ï3œÅò ¥h½V<Ð=?a¾¤ì_ÍooOåñŽ Ö»nÊ[Ly²ϼ×{ôFV,\ËÆô쳓 s/«7¤áÀèís‚#ëdìÀÏYœ|€sQ0ÀŽ;xøá‡Ï»nß¾}DEEåxùõÎhÒ¡OÖѱ²þgee&f°,Ì“Á' Óô`™ffö ibšb£Y¹xžîžrÓX´hÍ›·ºê嚦Izz:?½{ͺ Æ·Ëôd½ÜX–ç´÷ZæÁ²Ü'ÿÛ<¹,s{/ÿ‚,]²”F=¦“'´ 6›¸^ýs¾lܸ‘ªU«ªCDD®±Õ«WS®\9œNç%ŸFV­G@žPl6;†Í†ÍfËú׎a™ÿmØÀ02ß6 ƒÌ÷Y£IeþOÃ0t0DDDDDDβñ(P¨:Bä_}ð A!!×¼Þø˜üüüþóÏOÁðk35îá(W«ù%í£ `‘\Ʋ,222²2v=9ÈîÍ>ã÷ìá -O˜¦™õ£²~8‘œÉmÀrŠÒþDrÙÍÒ²,ÒÓÓ3oœ¦û¬àíYóÿ^êpÐYCK[f:¦i’––vÚpÓ""""""""""""ÙûÏB¿¹ ™Í²rÿhl ‹ä2‡ÔÔÔ¬!ÖÝìšgf÷šç†3—µ<ë–ly23ŒSSSñxaY¯|™/WhÖ¿!Y¯`l®¼Ø¼ÿyaóÄæ Àæåáå‹áðÅpø`sø`8¼ÉHŽÅãqceËÍÁJæÀ_KX¾3éú YÇùó«ùasœ‚\"g]ÓGvîà@|z¯tâíbÏñtõˆˆˆˆˆˆˆÈUJrb"¦ÇsMê3-‹äÄD¼ÿÝì¶™SxºIHN¦`á¹úøh`‘\Æ0 ¼¼¼ØtÔëóI™¼Wû&ì9áCìšûW®š´ßÓpÀzZŒÅàZ~g­õ°szoîèà©/Þçþ[®ñóGîL{á¶Ü7‡š%ü¸¬³Þ<Ìì‡ÛóæšŒó¯wÖæ¥ÿáž`]S"–Ê–y³øqkn »ßÀ „—$ªr%òzý;Õzްbî\ŽU}€"!Nt•Šˆˆˆˆˆˆˆ\‚228“gø7c ––…Ç´Èð¤þgŸÙ²,l6eÊ–Á}ß—K`‘ÜvQ:ÐüþA$$$àv»¯ê͆a`·Û)âëKHH^^^ ËU`sô8éžÃ|óî'tªÖR§}ÃXÑ?1~æFÒÌ"DŸ°à–ëð#Ú‚¹£ßxJ'X€›mŸ½Ì[k0`HŠÙ[ Åu-‰dw¯HKIÁ¼¥&÷ÜZ‡'¤GعqßÌÞH…fm¹£¤¿´""""""""¹\rJ yBC)QªI ¤edüku†v»ý?ýÌ^^^Øl6Ž;Fjjj®>> ‹ä"†aàp8 ¤víÚ깎XÄD‡°p ìùŒI ÛñFã ¬ N:ë?žÆ’€"I‰%&Ö²¾¨Íh–OÃø/—²5Æ ¤T]:>öÝj„b#•ů¶å™-˜3³¥l™õûâ Z¾Ìÿ{•FÎÃ,ž2Žæ­bÛQÁ‘·ÓíÙgèy¾’›ucÛQe,€“;†ýÌÛM\Ù´átNÂJW!,«,ûB'ÆÎüDV­Jy;`ç÷_àÃù³í`,é^¡4|n2o¶ fß—yjâˆKÇ‘§(5Ú>ÎàÞu ³V2[¾Ãði¿²áp*^yùý±Q¼Þê–¬ú-Žüð2m¾>Âád/BËÔãgúr_T€‚drý~ß¹òR¨`A¼Â‹S¦B9Šþð?Î_Há-‰ôÌvþ¹˜å›÷h᛿$5n«O¥üÞç/4ÛíM.šÆ¨E™—jч»J;.½ 11‘ÄÄÄkRWÂ5ªçF¡°ˆˆ\&±1qØ"zÑ·Ø'È­¾:óäa $²NEVÏúƒ¿·'P¶’‹ƒK¾á»­Ô¸­ýSÙ³b¿}÷;4¦ø9ÁfpàbÛÛl„UkE³rx8²ßÏ©C#""""""""×€EDä*H'6.G`uî»*ßNaÖx©®7Û¾ú˜¥¡m˜Ú¸ fÀ汘`Ä/búç{(Ý}&/u,† ¨Q%œ¤]™þÑïtÖ¿jõ¨æÁïÄÑ鮼)kX¼ÚC¥‡k°_£îóèÓ8PöùC,¹g2?¯íG½[ÏܸBŠQ²ðÉકƒ6ø_R?ø—¬Ám5Êqú`$þµi˜õßå˲}~W¾ý{žF 6†X+€:U«Q±ŒPæœ2ƒ"o£Y½Ì2«†ìgI÷X¼ÙÍ­Uõ5.7#(Œ0o‹Ý1qX©Yõw"Eµ£N)Â&°kÚl=Ø€âEÎÚ9uçÅ·ÏúÃ×//¡!A§²ç³Û¯˜]FDDDDDDDD®;úåXDD®œ™@\<ø‡ùaÏ_ƒ›N£ï¬yôŽÊϬ/RóáD:SYé qñX€¹g3[RÃhXõT@{8Õ«äcÂ’Mìñ4¢|ž:ÜYÝÆ°EˉkÝ×ÚÅ,K«Ä£·…bíÙÊö”d mMÍ×N6w† ïè$,œÙ‘ìÉI®8þ“Îþ…S;ãWÖí‰&ÕˆW’GTzæqÅ{x¨ÆBÞ~ü>64mKÇöwѸL^.T­­`a qÄÆ›:ï䯓5å½yâÇÜéÄÿ<™1?Ÿv«1Á+)ýÜ[Ð%nŸóý|tLDDDDDDDD亓kÀ¯½öëÖ­;cY±bÅ1âM ãêÏz˜‘‘A½zõiԨǻ趖eñçŸHdd¤Î"’HH_? |¨uo;Jtý„qcò²Ð«)cî ÅfÇÏ×"1! 0þ‰ô\Œ‘‡zwÖÄ>üŸh@ð/KH­ö B 8ØBhñâXz”==\jÃ74(‡óãZÿzϘ;fÓЧØÚ>ÃÐ~‘„°—Ï_Âoÿlà,Iç1ŸqëŸsùdÖ^ê:‹ÙŒeb·HÎ7©a³cÇÄ£ø¯Ü`¬Ø#I7È”y0ü(Û¤-µÂNÝÀËÏÄŸ{-_tû‹Ü.k?‘ÜË–[¶yó–s–íÞ½‹´´´K.˲,zôèIžíúɺ mÙmo8ðr@ZêÿٻêÿãÏs.\–ìéDCÜYZ™šÚR+[–Ms”šåÈ•9ÒÒ´Ô´4+µ_¥iÙWS33w*¹PÁˆ².Üs~°•iÐ÷‘;Îøð¹çr?÷¼îçóÉ,ò7@-ï~„B!„B!„¨B*MàâzügàÀù—+Ò#8>>ž-Zðþû1™Lœ8ɬY³Ø¸q#Û¶mÅÑÑ‘åË¿’#BTHllŒT‚¸­4M#++‹Ë—/ÝÞ‚èé¤e€Mn9Å™.Ï á€c2{ÖÉý´‘66hÉ©¤ë`åÐŽ§úyóÜ’w˜`ûúÀñuŸ²èd=ú /˜{×6Œ^]yaÁ\,œîgv[ûœÞ½NðÐR†|9†Q†Aô öĘKdR-zÞ„]á—CûYñËR¾ìC}ý Îí¹/¨œeøŒu}©£ËšEëñèê‹‹!Žó©…óðÝ¿úØdűëd 88á¨Êñ-î\zúeΞ=‹ÁœÅ•ÄNFâèe»u ‘øÒ,`7«÷üÌÏjKkØcÈJ!>ÑÀÆÕ1æ~ $ùÌqN':áãTÆòª3nì=º›ý^Mp#™+6>4ª^Æzåø]._¾D\\,–––¨ªÿüsbbb¨Q£ŸþM›6`ôè1Œ=€¥K¿ GòꫯðÚk¯å–1ŠæÍ›³fÍjÂÃÙä‰'ž ++‹éÓ§³rå7\¸p¦M›Jhh(º®óÅK™9sgΜÅÍÍñãÇÑ¿9‚o3OO/©q[iš†Éd">þöÀWÒÁÚÆ:èe«F½5¦ðB 66ÖèRHÕÁI±¦É‹³øÈú#æ.}—Àµ~žùƒ ~l¤iï4Xý9Yö¦eþŸ|;Z¼>‡ÙNóéO3öYTó¤A——éÒýªXq¤ë«#Ø3n>óÞúl»Ú´À½AõÊY†ëgh8I#㙺t¯¯LÅli‹£kkåÙY—Oð¿/W2ól& {j5nÇðwûÓÐÈ0Ï⎣becƒrf߯ޅb0bcïBõ:ÍèÙ-gËüç}ív½èe³?oáÇ¿L`e»_[ê7£â@ã°¦Dn:ÄÖC>Ô ¯^ÆòÖ4ïÄÙ_·³}]$šÑ‰z­½hXÝ¥ŒõÊþ\\\ñððÄh4J,„B!„B!*¥k¿Á¹#áé¹ÿtÐutt]CϹ€¦kèZÎuM3£kš¦åþ4“’Ïîm®»  $33GG'fÌøGGÇ"ˤ§§3räHbb z]~ùå—eÀº®Ó«Wolmmøúë¯óo?|ø0ááí˜?ýúõ»&ž4isç~ÌèÑ£ %&&†víÚáììL@@ <òýû?@íÚµ±²²*5nÛ¶-íÚµ§fͼòÊ+dggãçWoï:Œó«V­bÚ´©Ô¨Q“?œÎÿ°{÷.¢££iß¾£F¢k×{ˆ‹‹£V­Z2ÿðm¶uëïtïþ T„¸­òàˆˆBCC¥B„âÛ»w/þþþ×7 ÇÞÉ U5 ¨*ªªæþ4 (JÎeEEɹ®¨( 9×QÈùÄMNJ]žq„B!„ân±s=^5kJE!ª´˜sçðë^¡u,*ã/2`@9xð óçÏ';Û̳Ï>CXXÏ<ó “'O¾®íjšFFF&“‰£G1nÜ8¬­­iݺÍ5Ë&%%1þ§Œ92?ÌÍc2™ðôôÀßßÿšÛËâïïO‡ò¯'$$°xñbfÍšI¯^½˜9s&ÁÁ!üùçŸØØØ ë:íÚ…,GºB!„B!„B!„¢X•rœº¼sÑ¢E\¼x‘„„Ë|úét]'((躇×[·îgÜÜÜ©Q£&;w&>>žåË¿ÂÛ»Î5Ë=zŒ+W®~ÓßãÇOpåʆ yWW7\]ÝÁd2KXX;v䡇â•W^)×\ÉB!„B!„B!„Bˆ»ÅÝôˆ‡‡3qâD¬¬ŒT¯^ww÷—Õu½Ôm7Ìž¢( ²²²+T.]×QU•ùóçåÏ/œÇËË +++V¯^ÅæÍ›™;÷cÚ·ïÀرc>|˜ÁB!„B!„B!„Bˆ|•²ðÁƒxî¹çpwwÇÙÙ…—^z EQøçŸÐ4íº¶ëììD‹ÍiÒ¤I©á/€ŸŸ/F£‘?þøãšûEÁÚÚšÄÄÄ¢•©ª¸¹¹qìØ± •+o_‘‘‘4hРȷƒƒCþ>;wîÌ÷߯áå—_fÁ‚årZ!„B!„B!„Bqw¨”=€W®\IHHÌ›7/ÿö+W®°dÉ’[RWWWž~úi¦M›Š®ë´lÙ‚ÄÄ$êÖõ&00ÀÀ@¾ûî;š7o®ë¸¹¹Ö’x€Y³fѤIAAAœ>Uf`íêêÊSO=ÅìÙa0Xкu+ÒÒ®p™ÇœÓ§O³uë6ÈÊÊâÈ‘#8;;_÷PØB!„B!„B!„Bˆ;S¥ €/^¼È°aÃyòÉ„†6CU<ÄÒ¥_wËÊ1qâ\\\X¼x1'NÄÍÍÉ“'Äĉ^!n¶­[§{÷oú~ržsZþw…žàŠ’?7uÞeqgÑ4 “ÉDDD¡¡¡R!Bq‹íÝ»ŒFc…Ggi޽“ªj@QUTUÍý™ûº­ª¨Š ¹¯áŠ¢¢(ä\Grÿ“×x!„B!„(FÄÎõxÕ¬)!„¨ÒbÎÃ?¬{…Ö©4]HG- Å0›Í\ˆ=KBR WleEÁ ª¸»xáâìš !„B!„B!„¸»8<º”o{þÉÇ/~ÂÚäkã¡l§‡>ËûðÜÚòuFºgÆOôù¥;C~µ(öìu÷—3èðHYr€ûçüH×ïº0t›]…˯kÓ¤çtjˆ¯g5ŒW.¹‡ÿ-_È·ÇË÷¡lÕh‹5é\1é¥Þvu¹…¨jd a!*1]×ÉÊÊâRB<ÁÍ*ÞêºNJj2g/Dc6gãîæ)!°B!„B!„BÜ¥4û®ÅÞ‹š ½±O6å €Ôü 33ž£ïg±d•xÛ ,··‰ÀBTrf³]×Q“)³ÂëÇÅÇPÏÛ—ÓQ‘hšŽ§‡—„ÀB!„B!„BqR“Ò0Þß‹ök—²9ݻٮt½BìE‡º¿ˆ•s‰¸Û1Ûv¤GÇDvŒ[ÌÏ697&^&îL`yÃëéF•[ˆÛE`!*¹¼9€áúæ÷Ë4e`ceK½:~œŒ:ŽŽŽ—Gu …B!„B!„â.cü÷[–9=Ë]¾aËÚ,rÎ<«TëÒ—îÇVð¹ý[ô.´¼nð aŸ—Ü¥!~ÉÄÞÈן­bSlÞ¹e¶-^åÝ^ᄸfxl«,gíÙœ{[O\Ïó»ä™ÍÅ–G3֥ŀÁ<Ý·:–±DmÿŠO¾ø‹£éWõèUUTÅww;T4J º´íÕxn9sîÕ±`?>˜MÂ׃^mñ5·=±2åšrwý`á{·‘Ò¼ -½­QÎï`ýÂù,9œ…è5 z|/tôÃÛ:žè;8Õ0€+S†ñéYìš âåþá4¯c‹eÂi¢¶-dü—'HÐå˜7‡*U Då§ky¯Êu|ç„ÈÖÖ6øx×')é2qcòCe!„B!„B!„wE;Éok"pèÑƒæÆœsÄš±½ï7±íû\Ö wªFÝgfóa«6Ly‰§†-â;½oëE3cÞùewç8¶OÂÓ¯ÏeiRw†L@G[s9 ãHƒÁÓxËý{ È#C—ñk­‘LXëšE ©›ùñw# _Ãô¯ Æ nïüçýyíW…ŒóPϾ<±2¥ØÛŠ£¼ Jaÿ§Cyþù1L8Bßáýhom¬©9p:SC²nÒ <õæ<¾6w¦}Íœ¥³ÝúðÚˆ l׎åõ§1èý¯øzG IþŠ›H`!ªMÏë 'OFrôèÑ2¿OžŒDQ .î"¿mÞÀÆMë9|ä µjzs!îQ4R5ÛßCß.1ü>ÿ+6žN"ñü.ÖÏù‚ÿ³Œ'²s—Ê"ê×oøß©$’bö³éãOY©?Ì£í e–$ÛµýÛ`Ý¢íJ4‘yq7«¿ÞCZ«ðbŠ}™£Ÿ¾Æ«KN’ú<#fÅw㕎î8(×±½ Ëâü–ÕüïD"I‰'Ù¿b›í[ЬvfûûxìÞ‹lY°Œ_N&‘{˜m_¬åìœÎì\ o‹¢þ=Í…Ô4£þaçñT2õš IDAT9Ç&n*Zˆªð’\¨°¯¯_…Öm׺cÑ'½…ÙÙÙdggcee%•+*ÙÁžÀ¡­[Ø×y`ùBý çîãLµÂ|ìAÍoÄcPBVô±•ôñM#úà~ŽÔõ#¬¾ãµÏí¬ø‰ãuîg`ûZ9 ES~ßÂß‘±¤f¨Öè^žìZ›Œ çH4ÖÄÛÕx[ŽÓØ“‘œ«ß ÊñÜ7‘t;Ë+„B!„BT"Jö¿üü}4ƒ¾ÿígñëiÏþEqA¯AÝBËeת¿~ï¢ æØ5¤þÍîoС¶+pmoY5c{ŽÙÒ·Ž'[j9²½êPÏØ„ÀiK¹'ï-¿E5Ü Q%”;ŽÈµ3·ÎdžíérÿÃ<öæ í^äÅuW*¼½ÿBÍH$!à W ìš~4RòÃÉB‘[¡hÆÓëø6bÃ>Z@è®müþë:ÖH&CEqI,DP¸ðñã'ÈÎÎ.ûÉmaAýúŇŚf–Þ¿7ŠÏŸ~ÈÿÜ_à¾>îÚzHâ×wû3vc<™š‚Áh‡sõzµèHïǦ}››³ßì¾xûmŽ>ñ5-¯7.RvP-mpò¬G`ën<ùtš»ßeê¨Sq[eÆbÛŸû9q!‘t³kWj6 £K˜7ve¾4RÍÙ{«ÜÇ^ãÂߨ|Ö…¶÷ö¦–-d[:c4DzmÝ:.†¤Ž«±ÇIG7,ç—cidë ŒØ:¸âUÛ—  |œ-oN¥˜cÙu]åB!„B!îD:ÙÛWðõ£o2à哸f,ãíƒ×ÆEºÁ€Á`‰Eá7Òz6YÙ`6—ÔÕLV–¹\S*Ù™dš÷òÛˆI|u¹çô ’ŽüÊš#²7} ‹î ƒu›¯{×Y‡¿ˆŠªZ`¡Î]É:ʦw°7ðºßÓžûF÷eàI¼>uQšœ¥7‡ÀBT/– õë׿Û%¿~§pjÇV"ÛÑ%À¡ä°@»À¦…ñu·>ŒéësW˜™äÄ$ÌMžaþ0ŒY©ÄGfÛO_2tÀZzM˜Ã˜Žî•tÞܲ=ÃüW[c“•Æ¥3ûùqÙǼ¸ã4s–Ž ½4ÄD‘~œ?l&ʾ1aÛàj­“žCŒb…•Rô½IñïH\ y !ù “9w.ûú]iæ[½à9l¾TfQL—Nq<Õ™FÞN…>£‘™žŽV½%·­ƒ…9“´„XNFìá‡vëÅ=¾Õ$ B!„B!n2%k??üÀãCjrlÊïœÒ × ±eq!šH5œ†Ù¬ý'gD-Í€ÝxNýr °ÎÛZþ:ºECûdµæeEP–gŽ‘ý8­B­Yñ›ù:†DN'11=Û\¡í©ŠR®ÛÊËE$ i¤ñÃþ’:“dxh-_ZËòï_bÆÌ^ÜWý/ž“‘ÊÄÍ!°U@ÞЊ¢pôèQÌfs…·áêꊧ§g‘í‰Rdíbz¿>ìó0K €Eц’£7Á!ÁØ´hË}=$lÌ ¼7éCB§ò€›Ù1l[<‡…öp<ÎŒKã<ýæPú5.!ô)sùl|Ô‡¦¹gòFftµ®ð~T'o‚›唽Y+ÚûfÑçù5üß߯Ҧ‹MéÛÓ.ñÇgÓùì·ƒ?ŸˆÉÒN#1­»Æ¦Yù윉K";j5ëAÿŽFö¬ÝÈÎãq˜ªyÓöñŒŒ£ µf4¯/ØÉ¹$NÞ´èõ ï¼ÐÐâømÖD>ÛÉÙØDұţa8‡㉠ûœß­Ô²–cýâê´sÑë¶ñ½z¬+¿|?5§Ü'>ŠÇaþ÷Ãh.-ŒÛ–s™Õð 3ÍkæÆµu}ñ/úªBÊ‘_Xr(•”,;÷z4kß‘ÐêV Ç³cÅ7©Û—§Úz¢’MV¶Nâžo™µÀ€Wxžhšó˜Ÿßú3·æ4)ëß?˜‡XäïãJÔ.þˆ·W X;S³F ,j×£a ?Þë¿ã—ß¶PËëÛZ 'ÿÞÆ_GΟªcëéK‹öíö,aú‚2—/¡¼ÝB!„BqGÐÈØ4•·b $P‹ý̸Ååu¬ÜÚ‡9/¦ç¬UlŠ÷¢ácÏñHꗌݟ\ñyèi8õ#[.ºáóð+9‚gâçñõ?©dºâpœÝ‘EHÖìÚóè3µaï^þ‰ºH|¦.=ô`6í@-s{f.]LÀªUkš¹üľ4ÈÌ,î¶ì Õ¤EÂVm}”¹/¾Èó¾cc¬+u{t¡…á2›³C3îižÎ™'9j‡›Ÿ'®¦¶§¨rŠ›FNÏ Q^Šõ‚Ï*5lØð†nOˆ›û*Sƒ^ìËŠ'±fSÝqàÀ'o0ü×ê zóFy$ó×’é|0|5¾M¸õÕHg_iËs^ʘÆûz¢¢RÍ˪ìõìÊ.ºÁÆk%›L“¹ìíÙ$rx뜭ý<“Fã`NÁàí‚Ê)"÷îçRý—™>¶!–ɬœù S?nÄÃ/¾ÂÔ—l¸´}SçgaÐ7Œhj (¸6éÅëŸÀÃ^'n×r>øt<3¬bJg{Rr¶éû"SF5˜~†­Ëæ1ãm#u¿M[Û²ÊZÆú–%ÕiaV„´ ÁøëöÄÂÏC=‘þ‰Â*øü¥uqÛ¨N8*iD9M²—%|èÔÒ¥>mBjQMIáäî?Øòó¸ ìBÝFGr ìNϦ®(€¥­ ©€ŠG³éæo‚‚•½Å)8[7aïò<‘B£`kÎoÿµÇìiÑþAºTË j×ïl^û»Pïš]eq®´å ”PÞ2Ö“á !„B!„¸“Ï#˜Îq|PR÷=‰£ G2îÉÁ<õÎ罊mWŠ7wþ¼—‡Ÿÿ‚oý¦ÒëÈþâu^NÌ‹ƒ?áK7æ„S]=ãšXWâ8ÛŒ{û å¡ê®8’H8sˆŸŽäË?2s—ºRÆöÌ\Zÿ) 3bÁÃhÛ&ÓoοÅÞV1Éýìm&>óOŽíÆÓæ3þ3š3š: Û¹áþ ŸñÄÃ6‹ô³{ÙÏoÓ†1fÁψU𠾟§Ì`Dçê9Ã…jgY=t“~>Äɳñ¤Qš!2tÖ\^kå„RÖýYQ¬›4‚÷—oæà93Íz1bÖ,^lî˜sIåkÅŽá~†XÓgå%V=j[\qvù@}~†3©FªW¨ fŽ-x„žïmäÔ¥ ,ÝÑé¹iÌך@;Ç÷#žeòO8OºÁßV=yyâd†´q« ÉZ ihoæÏÓg1'_dÙ÷ióÖ§ î’S?ÞºÀö‡±qÿpÂ[]uì'o)}ù–9ËY»ÖÁÏ·Vþð´e®×¶˜TGËÆd2¡šR‰;½—ç­â¸UýBlÑ“-}{­sÊÕ|[о…Áp·¹Ÿµ°ónB«P ãufÛ–Õ£sïN´¶‚aÏúì;p­i]Tªùµ¢Sî&:pâ·'ùñ`æÎù/Üvu›Ò¶…?šÑÂý;žÝÀ¶#Ù´ñÛR޲–¼~Û âë”"vªµ§™Å‡lý+‘GrAÉŒ`ß¿*M^n‚üÙ¾mÇ&ÜÛå?oYÇ’“îø4ö§IPc¼-‹<~Ö>4¬ç‰ Ô´MâÔ7ÿr2N£nÍb·ŠÁÆWW×kŽ ;gÜ\oÈ( Š£V:§/'¡gœgÏÁT¼;÷¡uýœ#Ê£S §¾ØÉ±ó©W窕3N–¾|íÊ[Özu rP !„B!„¸c$óTþ9§béÙ1ö~vºI͈dç‘ì\XxÁ‚pwãˆçÙx͆ îß1¶{‘í¥lËM…Îh9¹æ}F®)½ì†Ô#üõíþú¶Œó elÏô'«Æþɪ2n»ºÜ¿ ëÁo…"5%{ŸØ¹gÂÔŒãlŸ÷&ÛçåÜŸíö$“»V'튂Åå_øbÂ/|!‡ ¸…$¢ (UÇ:%ÇZ¶ú2þõNÔP¢Y7mdÊ âÙæ¦~5ŒšN:ç6}ÈcŸdDÈq–÷qBÑ/spÓïœóŸÀ—Ÿ4ÃúÊ)6:‘á÷ qëVF[Vƒ8÷3æ¨cœH¿Â¹‰=hù~þIv–ŠU|Ú5CÍTtùò¯g¼&°ÊÜ:m'ä¶à,qò ç¥iÃéS]Å|ðúÊA1_77”+‰$d–€ÁWØ“’·g·,á£e›8O†ÑË43A¦’·Z£65•$“µëª³Âë——âÜ–ûZÌdòï;HèñÇ÷±?½!†9Ëé·•—Æ÷0 ~ç#pèÐ~Ø÷7ÕÃî§GËX÷ø;8â džùß§Ð.lcÉw{IÌ?”.0oöF@ŽÕã he[®¿ZÂE.f›HÞ¸ˆÙ ¿~eڵυŠ._þõäã B!„B!„¸u}hpy??J/_q›È‘'D ë=€ÿý÷ß2{F4hPæöÊ%i3æ¢Áˆ¿ùòí`Œ€ÉluZ|ñº3];t¢US; ´`ÝÄŸøpîaÇìgñ°FEþàè ?òá¼#4³Ÿ…¯4ÂtjWŸ”C¡|8c-o®ì‹cÎo}£tëÒ :ѱæi6¶ùŠŸ÷fÑ-¼ôûï ú?f.<ǽŸláݾn(@ÓO¢ØPßýñ1ÝÛ”\>ró¯úRúl *.Ízòè-°ÚyE²¡õ—å+ÃýV8ÝGÏÜ^—Í›:sèÛf|ñײû´Ê™‡Æ¸¿kÎöï½/¥E[>š¹W—ö ²ÏR©EGðoªÚus{“ª®ÜÿîG<ÛÈP¤mÝQˆ)¦zK[¾´‡¥bëY†¾ÀüW[ak´ÅÑÍ‹.6ù»Væö.—ÿ…×Ò0aÖu@ÅK Ð5 Ð"W0rÌ·¨½†2qxc\‰fÕø±l.e›ŠÁ€ ³VžßýrÙë—‡âD»n­±œø[.ßG“]»ˆõéBO™»¤r´ðì©Ñ°5†Ðt׬ük3{ë=N±(**:ú ˜^u¥÷ãÈF'õßßXëKïŽ>X¡`aëŒJfɯM‰±Äšœ\QÐA±£Q×^„y>¦,í¬ä«×.cù÷zë !„B!„BȪ՞Þ5O±ëPÉÎáôÐ+ë?d¶t•·‡ÀBT…{ì6nÜø†n¯,ÙGv±/­võ§¸©-Û fd—ï~o»—†ÃS#l0\:ÎÁuÃöS*ÙR5âv“RBT~úè–uÛÓÍÙ˜±À¢„Ê*ˆW×¥ûoËødæ,žiñ!s&nà×QͱеR†ÆÕ¹žßJ±°Ä¢”žŠEî×uP«óÄâ ŒjVøÏJµê.(Zée¸Îg)‹r—A;4GŸ‹á¹Ù|ñQs<•c,xúq~(kª 7ø¸¸´„SìÙ³«¬4.9̶Ÿþ §é=a8ÝÝT:0à¡¥ ùr £ ƒèì‰1#–ȤZô¼?;Åg »ÿÇŸgj^»Œå uhìgdÅ/Kù:°õõ $8·ç¾ ²öSÁÇÔ©ŒíÝÀ:4Öõ¥Žþ-k­Ç£«/.†8ΧÞ²–T§W?6v(Ö¡ôíQ“Ë's6¥õFòßÛË|a/¿FdQ£–޶q™“ûqÙ‹¦î7ðÑQñp³`ïÑÝì÷j‚É\±ñ¡QõòõšÕÓ/söìY æ,®$Æp2âG/ÛØ­ìÀ—f»Y½çg~V[XÃCV ñŽ6®ŽQ±ÁÆ’Ïçt¢>Ne,_byËXO)!„B!„B”ƒräK¦½ù¥T„¨4$B”þG·1õ óر#-̧øpG©†_×!Ìêú$}‡·¤óÇ‹øsXs:×&Àêc¶m‰$»UÑ!–-6#Øf.Û¶œÄܪaN¯«ìãlýã¶ÁÍh`A~oÐë.{ý&ø[ÍáÀq3>ý¯9‘¯—R>lm )! ®;Ô*« i?ïæÎüqOÑÅEÍú.eìÍÉŸ;.`Ðߜф+öNލÁ«/}ji‡sõzµÈì÷¦}þüÉv´x}³>æÓŸf2ì³4¨æIƒ./Ó¥{vjuxö ¶Lýž™ß·§õkAe,ïH×WG°gÜ|æ½õ;Ùvµi;8€{ƒê•¾^…Ãý2Ê}#k²á@&ŒgêÒY¼¾2³¥-Ž®u¬e_ÎÏ$üDz*%ÕiÝk ,hððc„}=…¿úÓÝ[âßÛÍlaõ•ìÙ²—¤ôl£-Ξ>tìÕš& ܰÏXÓ ¼gÝÎöu‘hF'êµö¢auëÜãTÁ)ô^ ½z=+”3»ø~õ.ƒ{ª×iFÏnAø8çõY7R»]/zÙlçÏÃ[øñ/XÙãî×–úÀ¨8Ð8¬)‘›±õuë—±|Iåu)c=9¦„B!„B!DÕ£tí78÷T žûO=g8]×rz ê:š®åÌO¨ëhš]ÓÐ4-÷§™”ÄxvoÛ 5*î[·þN÷îÞÔ}èºNjj*íúƒ®»”kàkžèŠB£F0r·Ü¸i=­Z„S­Z5¥Œ³Ûú%~BŸŸj3dÚXú6R8¹n£¦ï¤Óª‹, ÚÈ‚ß5›ÔÆÎt† “3áÌsüµo"M-RØòf÷-Txøwت–‰§I«×žÁðט6tþXá‰É spÙXÆ|càÍÛ™ÐÒ̙м%+ºmåДœùoIúŠž^/`ýUßô:Vúý}2ÙüFÝ?Óé1j ƒÚÖÁ*í ‡/ûòôÀÖ8(¥”¯I:+úúòôþ{ù`öKéQ\tˆGêoṖϲ£Ç·üýÑ=Ø—UÆ2Ê`»ÿ=B[ÍÅþåÙŒ{4w‹s,{¶«Úm rnG,͙м˜z3rØc´ª G¾žÀ;ËÓyæ—ÝÌloKæ…U<ö>ÏkÓ†òМ$ÌÆj¸x5¤¥¯s¡ MÁÚx‰ß¦<Çä¨ œv⥯?bBÇj;Ÿ³^ ‡ƒw² Î;IªžÎ¡ogð“]?>í"á¯B!„B!„BTÒXˆëT•zçñóóÃÚÚºb=€‹aþw2a¡Kè´9‚é­,å`¸™Šëa\ˆ¦i˜L&""" •ú·è¸<ÊÂg^dÁ j6íÁÐ1/Ó©ºAêEÜ•¤°B!„BT^ÒXq'ÀBÜny`²9¼úvh ð«éŒ!ñ0?|ð‡ë=ÍÜ …¸+òÂÒͼ 5!„B!„BˆJ.59Y*AqבXˆ*D×uþý÷_´ÜaËÃ`0ü·ÐXOâô_k˜¹òÑq)hÕjØñ9V|>–ÖÖò˜!„B!„B!*/gWW©!D•–š’Ráu$¢ Qÿ[¼SW˜¾…¦Ký߆ ÞݗλRB!„B!„B!„( €…¨b*2° ¾¾¾RiB!„B!„B!„w‰Jÿüóz¾ýö[ÒÒRK\ÆÊÊšÞ½{ѧOyÅ]çÖÏ,ªŠóçÏK%!„B!„B!„w¹J/_¾“)“ï¾û®ÄeúõëÇêÕk*m¬ë:‹-ÂÍÍ^½zÉQ&„¸%jÔ¨!• „·XLLŒT‚¸céº.• „BqQE*A!î•&îׯ_©×¯–•eÊ_¦´°øvÐ4Å‹—еë=‹ÒðRUõ†nSUUiÐ !„BÜå$àB!„iÊùD!„¨:îø9€5M£{÷ûÙ¾}{±÷[YYqäÈ¿¸»»ËÑ *%UUquqç·ÍnÈI:EQpsñÀ`0Hå !„BÜeþ[{Rc!„Bˆ;KÅÝÂmI ƒ…¢r«4ðÍêÅ«ª*Ó§@RR ,dï޽̛÷ ƒUUqrr’#ATÎ&˜¢`4ñ©ëG ¯Zdggÿ÷'½…ÖÖÖXZZJCM!„â.P¾ÐW¯ÐÍB!„âŽh)\,ñ4¡RfSÎ1þ7ë¾_Íñ‘Œ›4‰ÄÄ$©!Ä ¡V–‚DDDЯ_?ÆŽ Àßïbúôxâ‰þLœ8‘C‡ðî»ïÒ¯_?Ž?^æ¶›4iB»víh×®5kÖÄÆÆ†ððpÚµkGëÖ­™6m¡¡Ípuu£zõ¬X±€E‹ããã‹““3¾¾~Lœ8³Ùœÿ·dÉáä䌟_}–/_^d¿ß|ó-ÞÞuqss§sçÎüý÷.9âD… ìììprrÂÕÕõ?;99agg'=€…B!îpº®—þêßúUWó¾)î[¾äK¾äK¾äK¾äK¾ªöW1m¼Üöß5w½ríNQ–ú~¾L{"ÎÎÎRBˆ¢Òôþå—_èÚµ+k×þÄ£>Zd™®]»²~ý¹÷Þ{ù÷ßÙ´iõë×ÿO'E~þy=¾¾>Ìš5“ììlüür¶×ªU‹}Ž££#[¶leâĉ4iÒ„ž={ÁðáÃ5j]»ÞC\\µjÕ*²í 0dÈfΜÅÀÙ³g7ÕªU“#O”›¢(  l…B!D¹ßã”ro‘W]¼æZÉçùäŸB!DդߖS®nåÜ n*WÝ_L;Tz_ŸZ5k2uâxÞû R!Bˆÿ¤ÒÀy={Ðu'"ñññ)²ŒŸŸŸ¾Ž9zCöïïïO‡ŠÜ@@@ÁÁÁ¬Y³†¿ÿÞEÏž=¹t麮Ӯ]8ÁÁÁÅn³iÓºu»OOO:wîÂþýû —#O!„BqÕþêÅý ø$¸Ø{‹ÝœB!„¨b½7ç†ÄEBa¥èrŠm“J\ºu߯æxäIƽÿ~þmQÑÑxשäqï2ú½q7o8h¥ŸäÉN iXχäÓœ<ò'?¯\˶‹ƒÆêo‚ŸÂàµpOÙÉÓ§óÕù†„<ù/´/¸my´•< âŽ3hà–|ùU•þ*Eœ™™Irrªªââ₦ide™0E–³³³#--gggE!99ù¦•kíÚuÌž=‹cÇŽceeEJJ -[¶ ,,ŒŽ;òÐCѯ_?žþùƒ`oooTU•Oî!„B!nŠâÃßâ‚ß«SàB·êŬK y¯ ó'„BQµ(Ê5 ;¥H{¯Ð5¥p(\ç-WV,!p鎟ˆ¤¾Ÿ/“ÇË¿mô{ã˜<~Þuê0ŒÎ IDATnÌÞ1òÆ¿gPk4d"ã÷²ö«O˜r"Í3€¦=žbø‡!LyŸGTtT ‡2±ÑÆ¿¹™ÃÙÖÓ¬±¼çM&Ô_˸¡¿a¶Å˜f”SÜ‘ú>Ü J‡À•"V%ÿræÆŽ¿¶ð£âÄ .u°”ªU„^RX«_Õ‡77è-6ôÍßFîmל,qÒ`!„BQ%ÚŒ…®”’Ù)ùmD%ÙkÂà"Ap¡¡¡‹é ,!pñ“Šôøò/GEG3nÒ¤¿SÅ‹V÷4Âüëx6\. )Z,Vn`ûGñ@£Õ\ ÿ9]54f°ªƒŽñô"æD¿Ä‹ÍÀlVuÖ±Šü˜WFmá¸&¯¸sUå¸ÒÌ\§N.]ºÄùóçqwwÇßߟ#GŽÐ¼y³üeŽ;FãÆ8{öµjÕº)åiРº®3mÚôéÓ C‘á¦O:ÅÖ­Û ++‹#GŽàì쌪J/7!ÄÝKuò&¸I¶ÍZÑÆ+™‡†®ã§ž'¤¥%dǰmñnØÃñ83.;ðô›Cé׸ŠÇo³&òÙöHÎÆ&’Ž- Ã8tOÙ£ µf4¯/ØÉ¹$NÞ´èõ ï¼ÐÈ[ÿœ‰K";j5ëAÿŽFö¬ÝÈÎãq˜ªyÓöñŒŒc^Û´´2ÉC*ÄA±r¢FźuèÜ¿?ª!·ýV¯jÜ)ÖÆ\ Q¯ƒ»üU@‰sþê…âÝb‚ß«C_]¿fż¥òo.¸( °B!D•~¤+…¯\DÉi¾½˜0¸pLîÐÐJq€óÛ¬_+/^¾d1@~ø{³æÿÕ,hX7†c«ã0cwÍý–ñ»ÙÓ‹¾õˆšÿÏ]YÆ2‡·è=7›Ø›ú‹¬ß¤ï¼s¹·Éã*î|U5®4phh3öíÛÇÁƒ‡¦U«VL:•úç/³sçß¼ýö[:t€à›Ržàà`f̘ÁÌ™3™?>F£OO||rz ÇÅÅ1{ölN:…¥¥%!!!ÌŸ? Ìf³<#î±±1R â¶Ê™3=‹Ë—/UÊò±%ƒŒ Hgß'o0ü×ê zóFy$ó×’é|0|5¾M¸M ‘{÷sÉ÷E¦Œj„1ý [—ÍcÆÛFê~7š¶¶ ®MzñúÄ'ð°×‰Ûµœ>ÏÌ«˜ÒÙ…Üõë¿Ìô± ±LŽ`åÌO˜úq#~ñ¦¾då틘:o< ƒ¾aDS˲Ëd'ǘwMÓÈ›ˆ£ðÔ#ùá/€žAÚ KG'ª•ó=ìåË—ˆ‹‹ÅÒÒR>(ný‘]ʰÏ%…¿:z~ð«îõ« v‹\Ö¯Þzq‘C!„¢2»*|Õ ü¬,“s{Á<ÀJ~àÜE(< ´‚®äöÖ zô–á +ÝûK,SHK/þqP´K$$[bmm ¹ñ®"‡)+«Ê•¹ÒÀaa-Y²d1»výÍ€ý©Q£sæÌ`ÅŠåÌž=+ùýû÷¡ª*¡¡¡ÚÏ´iS‹\7 üùçöb—4èi zº„ò†±oßÞbï+n›ÄÇ_”gÉÆÓÓK*AÜVš¦a2™ˆ¯L°ŽfJ%îô~þoÞOœqçµ&–èÉ›XöýEÚ¼õ)ƒ»8¡ÞºÀö‡±qÿpÂ[ç¬mW·)m[øc -Ü/°ãÙ l;’MÛP ªùµ¢Sî^:pâ·'ùñ`æÎù/hvÞMhê`¼ÎlbÛ²ztî֖݉@0ìY?‚}Ρ5­‹’¼¥ô2µ5ÊA&Ä ;ò>žóKþuÕ¥O hƒg‘¼ÖLÂáßù+΃Výü°)ç¶]\\ñððÄh4J,*Åkp±áï5½~ Ývu¬7,t^p,5,„BQ5›‰ÅÍýKN›Ôêyóÿæ¶ûŠÂ9ám‘E õV”ò…À¢(''G&—=*:ï:u˜<~ÜMé¬d¥‘¦9ãa_|Ã^W°·Ë"-%sbBäYñÍ·,_ùM•+w¥ €iÑ¢%;wþE¿~ýʵNXX+œœœäèBˆJ"ó÷wiÝâÝÜV¥· ^¼ûÑËtvR0<Ɖô+œ›Øƒ–ïç­¡‘¥bŸVì9eµFmj*I$&k€‰³[–ðѲMˆŠ'Ãè€eš‹ ’>‘¨âæá†r%‘„ À0¸âá {Rrö§E•U&£¼Uâ`¨Õ†~íê4|-ìp)’ÕfséÐ/¬Ùš„_·Þ4s3H¥‰J¯ØyK ‹ ~ õ.øê× ­_³«¢W%B!„¨Ì”«Ïn(Eš9K(€®äf¿JA œÿ“"¡n~<¬€®—/–^ÀòÂß¼ù"s3†7FŒ¼¡ûT³râŒ;ÝkcÜsùš>¾f‡B¼"9z" €…ÈQUÃ_¨D0äô¸½té‘‘'JžË*÷È×ׯÄÞ¹B!ncó!|6´ 6é‡YüÎtþ²ó#Ô×® ©¯ºrÿ»ñl£Âኊ­›# —¯ý{o0`@칂‘c¾Eí5”‰ÃãJ4«Æesi/r–€ sÞ6Š%– kZÁ©êRË$„¸#NvX9àáéYÌÀfm`ÕÖ$üº÷¦s=;yî‹*¨äð·`ÈçÜ0W×Ñóà‚à7¯‡o^P\°©‚žÁÓKà+„BQÅZ‹W½IÊ q¹¶K/ynހυÒb ÖËëù›?g°.=+bܘ1ùsþoîÍŽ-Q¼öØúü:“¯/Ї¼{?@‡³Ÿñf¤<@BPµÃ_¨d°««+S¦L–£J!ª(¥š ÔÇ?ÞÍS¯Ìeìò`>Ò‹Ú~ø¿áX´F­n¾×1ZéÛÎ<ñ/'ô`Ƽða hvÔqøoo e•IqÇ3ÛÁ[/S¯[ E•QÚ‡e¯îù[8üÕu­h¯_]ÇlÎÆ”‘Nv– Íœžçìˆk;ë+B!„¸íŠïu«¹XçôVÕ`…¥£µ ƒEÁÀùíB57ôÍÙFÑžÀ%·e¥0Ô÷óåxäIƽÿ>Ë—,Èïý͸I“nÂ^527ÎæÃñŒšô6N_üÀ¯ÿÆ“äØ€ nÏòRÐ.¾šü;Ç4 yÒˆ»^U¡’ÀB!Ø5Ìø'w3hÑ|ÝnOÖëÀ€‡–2äË1Œ2 ¢g°'ÆŒX"“jÑóþ ìÊØ¢±®/uôoY³h=]}q1Äq>õ?–Ò©Œ2)±?aàŒ3<8s1¯3Ç–½Ä Ë­¼ð#÷V4þœþ4omfücèì$od„¨ô"vþCZVØ_áâÅ+ùMd;gg줥,ªÆ\´÷o~|[tØçüðWÏC×42®¤Rß§ïAÍêž 9'„B!rZ“ºŽÙlæÜ…XÆLø€§Î`m[ EUÉéÿ«¢£‘Œ­ ä·H¥pIèÝçšÛ¼ëÔáì¹sŒyoü Ÿÿ7’}‚?§½Îë>EÿÞdÜ`;ÔøHNþŠ)#6³/YÞ Tùð$BqÓXðä0ùíe>Ÿ»žn>H‹×ç0Ûéc>ýi&Ã>Kƒjž4èò2]º—dÒÈx¦.Åë+S1[ÚâèZ‡ÀZöÿá-„]éeÊßðÕ“3Û¡.= „¨r´xbâ²ÉÈØÆÊÓ…nW] {ì Â=T©#Q…è…G.9üÍíù{%%‘IK‡6˜ÍfÌf3YYYRB!„¢à­‘ªRÃ˃e fñëæm¼ûþ lír{ kŇÀ(£IKð[!gÏãíwÞ%!1ñ¦îGÑâ9õã Þÿñê{ O–ÅÅ¥Ó½ÈýYÄ,~”ä¡¢JPºö¬=a7XÁ tM×ræLÔu4ÍŒ®ihš–ûÓLJb<»·mw­[§{÷¥"Äm¥i&“‰ˆˆBCC¥B„âÛ»w/þþþFTµbqãÐpìÜPUŠª¢ªjîÏœ^˜Šª¢**(9ï)ŠšsžEQP ÆhËiÔK¯Í»FÑ]=÷oÑyóçùÕõ"áozZ ï¿3ŒÎí[c2™¤R…B!D©EÁÒÒ’¿ÿÁ{“gccgŸû%ï}Š’ÿ¾%ïýJÞ{—œH˜"ï_nå{˜ˆë©]·n¥®ßu߯æèñãL˜<å¦õüBTmgNŸÆ?¬{…Ö‘ÀB!„BQÕ] ÷•sÍœE£úuéÔ®•„¿B!„¢|MM]Çd2qO‡¶|µr5Qç.b°´„B³DM)<ü³Ô_YŠZ!þ+×Nˆ*ÐÀÊ’/33³Bß&“‰¬¬¬œÞú24­B!Ä×VÌÿ¿po`rGt*Ü X]Ç”™Î{o•áž…B!D…eee1nÔ0L™éEÚ˜zþw¡6ª^ÐN•³’BqëI`!*9³Ù̅س$$%@E›KŠ‚AUqwñÂÅÙƒÁ CD !„BTQÅ ÿœw±à6=þßœómy!pÎúÙY&¼<ܤ2…B!Äu©îéFv– ]×sæûEÏ›˜òç¦à¿¼^ÀWÍ ¬ëºœ§Bˆ›H`!*1]×ÉÊÊâRB<ÁÍ*Ü(Òu”ÔdÎ^ˆÆlÎÆÝÍSB`!„Bˆ;«ÅXèÿ¢íÀÂC?çõÊÐÌæÛ‚º®qúô)bccnùðÐF£OO/êÖ­‡¢È@UB!„¸­¤JÞ¶‰‰‰ÁËË«ÊÕ«ªªhfsÁ‡óÓÞœnkŠ{‘± …âÖ’XˆJΜ۠R“)³ÂëÇÅÇPÏÛ—ÓQ‘hšŽ§‡—„ÀB!„w½ð%ýªá !oèüo]×Ñ4­Äí>}Š””Z´h…Í-ý]ÒÓ¯pøðANŸ>M½z>òà !„â?“¶ÍÍ“7Ýœ’ßÍm*sÌœþJ,„·–ÀBTr…OÔ]Oh›iÊÀÆÊ–zuü8u/ê !„BTéFâU?ó‡&¸çœ[õüÞÀùw– 66†P22Ò¹r%í–þ:ªªÒ A#öïß+°B!niÛÜ䯍®£+zþÐÎ J¡a¡s#ßb‡–cS!n €…¨ M*-ïDÝõµt]ÇÚÚïúDž:†ª(xæ†ÀâÎqþüy©!„âîh–rÜŸÉŸû7ﲞ??pñL&f³¹Ô^Â7KÞ>oõðŒÿÏÞ}ÇGQ´ÿí•ôRè!¡CèE:‚ˆØ)" ¨ˆ¯JS•¢4)‚€‚‚+¢ ¥ƒô!B ½÷+»ï!!H çû1’ÜÎÍîÎÍîÍî³3#„Bˆ{—´mJ°5šÝ®ÌžÇWËjƒ*JöœÀù ]ð+B!ŠŸ€…(T-»0c6[nú£Ñ@@@QQÑ\¾ô;žT÷¯ÉÙógðp/‡N§“^À÷ *H!!Ä!… î"íšß®þ™¼7çr~nÀjµÞµ½¹›ëB!ĽIÚ6%Õ Í;Íd‚µë‡ÎÓb•ûBq§HXˆ²Ð¦ÊÕ8  z‘ÞÛ®UǼ½Á€ÅbÁb±`kk+…+„BQ¶[Šy»çŽ“3tÎ0К˜B!„¸ýhÎ#Ц¢]íù{í@†¹æBqçHXˆ2 wà  ³X,7ïl0¨Q#ÿ`±ªZo8üß½W€—ÙüÙgì¯ñ#ºù¢Óbرx[Ê æÝžÕЗá}ÚWýu^ïä!‰Bq»¶í¦]Ÿ §soN W»&­ÆM{ßWmD!„BÜwm(Ql›Ó¡7'´«vå—¬ù€³ç.¸°–=„´Bˆb'`!Ê€«s•(Ô¨Q£ó»OXϳ~Îlv }·»ù‚Îß g²ê‘î¼Ó³ZÙÞ§—û3LÀB!Ä}.÷Pй‡€Ö®}¥°B!„(ÆÖ§Æ•Þ¿9Á`-gè¼óKï_!„¸Š5.%*DI4ª® ­( §OŸ¾¥9D<==ñññÉ“_¡Y/ñûÄaŒ^´‰ 7ê>ö³ç¢½—R<˯H9²þ$el0õ•¦¡B!Ä ‰Ü4 «åš8O·à‚ÒK/!„BÜKÍ%iÛ”hCTS²B»Ú5³ýæ“ZFBˆ;¬XÀåË——÷  Ówl]ÙC@ÔªU«Xó»93Ç>éMï™Éôšº’åϰdÌxžêçÊþC©¦»Ýå`?Âs>äƒÙ¿”ªçA©^B!„»iÔ÷JoßœÎÀÙ}4 1B!„¢íѬ ¯–5Œ3W Q²çVnÜ–•@°B”8e MuMà'NùçôéÓ9sj©pæ6ÎßO…W¾àó×»ÓõéQ,ý´/öÿÌaÑ~Ëí/ÇÊùeo2j£ƒ¿]Àsî…hª‘l5vÕ=±·±Áѳ* Ú?ÃÜWæFÎ8Ãw#¥ž#¶^Ô~ø6Ç_»Ï*1¿¿K§šžØÛ:Q>ð ÞYLföbÓf^«hGë™Ád‡ËÕóshg_‰a[L %shñK´ ðÀÎh‹K…úôý:„CëÖ¶LïG«jîØÛ9áU½#“v^Y›õ2›>îC˪®ØÙ¹á×òy¦n'O?ïBí“B!î˶bîß´k–hùM|ƒ¼4í®þ!„(›Nœh¾eôK«Øj…Æ©üùö£ô_åÊ€Ißði]#—v,ãã=ZÎú³[¢é™ž<4f1c+«œ\5™÷Ÿ{ŒŒ?ö1§£ÓÍËåØl^¾Š,çŸG* Fž&±rùžªI翉ñÄl3=?X̤fd†ÇàæoÒÙóþ£<9O¡ÏÇ+˜ZOãØò÷ûÄã¤ÿ³ƒ Íí@K,Ä>¥Ë"„Bܴ½~í=¹ÜÁàbb2›ùãÏ¿øwÛΞ;‡Ñh¤JåJtl×–GéŠ^¯gÔ»c9qê4Öþ Båº=3ð°8 ýñ±+ÝÛºdÙW,ýz9 2kÚ4ìííäâ¾h–jhʕ޿\éÔ{]ïÞ‚ºûJ7`!„(i¢L´§®ö>yòäMç¶±±¡fÍš7ͯPQDãEyoýÕíËSÁSãTx–Û\®RNWàp–´$RMjÖÍKÅG~cæ¼cÔµ—ï4ÄТ‚™£[•µo±ëølÅ%ZLü“/†ûgåÛÞÃ_ýÆŽ<9ë©Ü}$ï nŽèÚ¹>'2uÁoLèø ®7+—˜(b4wºtx;M Þfm=ŸÌ;Ný±‡X:¢vž¯ÿ Ÿ,8Eƒ±‡XôzmôÀƒíj|¬ ŸÌ\ÏÛkzáRˆ}Ò’þÚÜýºšÂßß,cÕ_ÿqêRfªÔmF×þÃÒÊK†B!îÎ7t>¿Ý^;16.Ž “§p.$$ç5³ÙÌ©Óg8uú mÞ‡cßåÄ©ÓEn !Sê9þþv ?o=È™Ë ¤+xV A«n è÷Õ,Qÿ±êËUlüï —Í\|¨ßŠž/æÛ\ygó÷ßòó¿9}9žtì)W¹&Í:ôbÐó-)¯/Cå’üïöžÊ6KM^]:ŸþUJï•ÔÚu¿°ôëå>r”Qï½ÇÌ©S°µµ• .JÇñT„6Èã={*ÝúŸ¾—r½Á_¢ðªùùQ¹R¥ëF̶X¬\ %ìÒ%)$!Ä-“°e@î»uêÔ)Öü Ó¤ÓnøDÞí.¿‘LþV'¾ŠËjJÚõfÍ_Õ8˜Z‰Ç»ÔÅ&ŸwX‚ŽpÒT™­«-ب õåIßt˜³–ghz“äÆÖCÝy-#d_Ÿ—yõµÁôhì…!¿mþÓƒ©•éÞ!ຓ®åô~§e-˹a¨A‡v™ðÛ>ÎXzѨûd½pú®×S-ùó†dÙEìñcTÀ>3–sG÷‘˜nç:…Bˆù¾þ÷¼£@í†\an’Z,>œü1çC.àWµ*Ã^J£XU•#G2oÁçŸ;Ïħ)_!Ä}v·“OޞĺÌ\ç­d¢ÎbsJ%Ð2޲päXÖ„ZÐN‡5áÇv ÅÀ¡yòšùö$~¾&¯È³øÓØ„þ}[–±s»‹µôŸ7·íØIçN²þ·œz¼<º-“Ó–Bˆ’o!Þ¼e¡å“´š ¿ýñ'çC.P¥re¾ølŠ¢a2eШA/üŒÁC_#(8X>&!D§°þùl&¿„d¢¸Ôã™á¯Ò»eål2ˆ9Œq•hb–Ãÿ²9Ì‚¦÷ã¹Oæ0¬± ¤^æø±*øëóäµ.$űO¾ö*Ï´ô§œ>ÈsG9M#*e7ÐÕX|ÿ%K~ÙÉÉ(åëÑ±× †>Q°žcí´üz2ŒÈøDR2œ|ëÐþÙWxý±86 €5–}ß.bñ¯»8cŹb=|n(¯t À!' ’ÌÉ ËXüã6‡%¢9úR¯ç(fd_BYN³pÀC,Œ _ç»9=ñ)%”E_.å«ßÐýñÇùtæ Þ5†'OÒýñÇiܨ¡ÔqQ&íݾµPé"##JåöûW«Æ‘£ÇþöæÍš‰¢(¼1bAgÏRÍϯ䚢ÙÁÝ\AÞ¬î 2ôsA:´k›çï·m¿n¹N§ä¤»v¹B†€…7¤÷€–>Iü½å ™ÝÀHÙ¾‰Ýæj hYãm.¿q LÁ£v:äšöX‹¬C ýví EméÝû µšÒÐ~.›ÿ:‰©Uƒ|{ ç+ó0ýs£¦Ô0V/|½4.œ:KµqÌwóœ¨Þå5fw@¯‘-èôÙ—ìÑŒÎ×nsLCêÙ~ƶƒ±:%Åmç— ¨ŠÍgx—쇃m©P§ ®¤³:9ã¬@¤ÇéýG¹X£~NhвB>yÙÒxЇŒz,û:Ó禾TÏI˜Æ…#yë‡ ˜1`ïhCòÅý¬}‚3‰sXпF5’£;r* lÝq±O%>ƒF« IDATì ëfG绌QÍì¡0iHãà¢QŒø.‹Ñ•ò^¶Ä‡îç‡écˆ·YÄÄN(dp|ÙhÞXqšttØ:¹`—EŠâŒ1çšÓ7_o\ `ðv¦´ŒbüX·~=³§Oã— xþÙgP îˆRt¾)B$""¼LïëgsfñÊëà >wžáo@QΟ£BùòÌ›=³äÚ˜rÈß–´´´ë^KOOÇÞÞ¾DÖçÔk)ßôu½ú]s…!v%]‡ütÓ÷ël°ÕÒI7_=¶ºLýšA'ÇÐçë’{8ân­WÜ¿^ìßeWÚ;e•€…(c 3ðµE¡víÚèõ·p¹hÛŽW‡5aùäW^ý#ž+Ä’Q«Hï8‹—š@w›Ë‹HñîÁ«ÏLàéIýxËi<½j+œÛð-G,ð  ¸wgÔëué4õižã^n_Û”œIÑ®k¡&ü—?¶¤ã˜~žÍŸO`æùÆŒýªÎúZ<Þ½“¦½Ã+2éßÐÎ%æÊýMkðz¾øG¥~ƒÊ8š.òωxð(‡G>Ñ?¥ÜS¼5d ]'=Ímý¨€1!„Ôj½éÞðIF«M§Ÿc¨ÓDúÕÓ8º|>©FÿÅW¿„bV¼yhì\ÞïìFøÚ÷yiÞ^N~ÿ-;zŽ£cöÓ¶JyžžúÃëÄðã¨AÌ:Íöí§x³Yã\ÁÙ‚Óè£ÿæ«u°ëòÊ¢Yô÷3¹þ=úÍü­¿n%úÁ§(û7K¾?CºâF›7f3±{UìÕ4RÍöèL§²Ö¡÷£Ï´Ò5ðÂÅKX±rUž×Ö­_σÚÓ÷¹g¥r‹2íÉ^…«Ã¿üðm©Ü~w77>ÿl^N ¼¯/Ÿ6OaðfÏ{h¹ÒH´øZÿnÛŽ­-¯¼4„ömÚ ªV6mù‡oV¯Áb±\×S¸¸ØÁËÓŽ“{ð Í\ˆwÚPeÈJfe¼Ä³KcÈ~Gèþ=lK)Á’º[ë÷³^={”é °€…(cŠcà¢1Rä÷|—:ŒÑã{³<Õ•º}ÀÚy/ã¯+ŽåE¤xòļõ|á0’Ù>ÇñöÔhQ£¢C¯°§å„ßÙèù.ï/|‹ž“â±ÚyP©F{zÔqͺ ¸S¯C[|~™Æ³Äc2xàß²;Óÿ˜Â°&¶9§Ç†£W³,öu>ü¨?kâ­Ø¹—ǯe'ûè1Gá§sK¦ÑjMŸ`ÖÒ‘4Ê÷¬êL‡i°Îs4|ñ=ǧaôªÍÓŸt扆•x`âÖ9Œ`ì´~<> åÝ_f1ºEö“~ïÓSÙû”å;GÓ®4ã nÈ[/œât†76©tõ¦Œ¾2Í{ñùŽ“\°v¢Þuo×QλJZñ€Ð{âí û“S îÌdS“ÖM=Y¾ï$¡ÖöÔQ Ú¦3œMOãÒ¤'h19çÖ³Û˜TôÝzòbó™ùúóïúÏôz’εÜц/B!îô÷pß$«›ç¥Ïjt¡ZÕëÒïÝþo¾ï —ÏI‘ç\”ïÙÆrŠoÞÃ/ñF:~°Žtã×°ªã~Ýð'm;Ä©-«øxëlñ)S­ŠR¨p‚õìIN™4tnÍy¼½7 òÃi¸ð?¶¥œáx¨•ŽÕ¯=áyQ¯Ž7º¡$Æ%`½rIr³4œ=Á©L M;Á°0÷Nd8‘Vp:Ɖ [kž~¬*ö  wÀQš©t~j‹—.».ø ðÒ iѼ™TkQJ›I÷ÏÀš¦66WÇŽ3((%°ÍZQ6 dd€½ÞWŠzÚ¿µ”Þ* ]%žž³‰§çÜdEvµèóé_ôù4¿…ï±éÔ{…/CceºŽ]M×±ù•OEºŒ]C—±7ú òß'UU1™îþÝWe*Ù[9tæ&šÐ øÖZ•£0aÕ®<)ª1²æl¾QŽŠN)Ü q'¾ÿ)ƒkçÝêp(çŠbãNŸ9ßÓfï¾]¹š¬dÕ«ŸòÅÀ:ØÚ¼LNUB!îaþÕªqìø Ž?AËæMó,“@¯¢P×å+SQ¯i¹ÈC1 ðó¾ÁT*F¼ë?Ìàú3hx릌â“m±ì\½Ó]_¢–oEÊëÂ-¡ì?Í¿zßbìÁ`4f_|xýq]UEÛ:?ÝŠJ¹.5—úx믦åúíÍ jk¨¥$δxé²|ƒ/½8ô—J-î =Èv­ÒÚÞ‰‹cø[#¹p!gÎßó!!¼þÖÛ|6gžò!—B­h Àè±ãPUO¦|ÄC>X¢àÓãÐx ¯ôiEÓÊöã/pqÇ&¯:‡íÀåÌyHÅÈB~|ÔBâwCèÿ]*Í?ø•¡ûŸdÈú¬/­§, ýá$7yfõ˜/nç·%ëØSûEu©Gír™$ù‰/?_ÏîD#ªMMZ>ÿ½[TÃÏÀs’?,dÞ?ñ¤¾/~½ín#©i'šW¶E¾›?¾ü‚¯OXÑÍP‘ºÏeH{üìb¸¸wçjÖ%súh¾¸$wóDþÊrXÀB”1w¾picáøóÙ¥Ö¤zEwô ÇY7ýSŽWȼFF© w‹]3ºuðàÏ?¾æ§géSõú™ŠõUëPÓö{¸„X5ë&‡õ"ûEcW£6Uõ€ZLÛ£†qøH,¶Õ©|ƒ|õ•«ãoó-gBU*=@¾5H±§JË^Œjù]>Èï~áPß:´4Þd™Bq·ÜFÂôéҹǎŸ`ùÊUÔ©]g'§<Ë“SR˜ðÑTÎóów«åóB\GçÕ†®M¿äàîT,Ì·7éצn׆XÕöÿsçúx; 7:SÎ-ë¡f-=4t^-éP÷ Îäà—˜mÿ*Ï´¬†‡ÑLÂ¥3Žñ¢Kôu©móûÿcýÖ(štv'üÏ¿9dÑP«S»rñå£÷ Àß°‰£æ2}:Ò÷I0'D`プjÕª6q,q¿n§Q×òØ`!%ÕŠ£­#N6 ¤GqîB*ZUg4‹ îÆ`ÐoÙ’o bÈ‹yñ…R¡E)o¾]tùòå2½¯#Ƽ›üýü³¹¼òúœ áíÑïðõ’Ew®-*=~ ­Ï€9¿7iÔƒ¡dÇ—ÓôFlmmÉÝMH3›È´jX<ŸfØÛu°ÿòFîK Í#€Z6‘$j .À›öß2+chž¡˜¯Ë_W…†uÖóÙç#XïMõ¾ãø`ZK:®ÿ‚/伡1ÝÞ|‹wûž û‚‹(3–˜=ü¾p '.ƒ]ãÁŒ|ùMžàvDe½õ“X°h$Kc=¨Ôë=¦¼ù4çßXÍ¿ŽøöÊ”úëX0õö¤U¦v7Y!”MR ÅM”Õ °€…(cÖ“'O^7×Ú /<õú{+h¬%²û'f­9FhT2ªSEêwª%ãie'uä®Qœh÷Ú[Uu@*;g dÌŽ†Løj,ÜäI!D16›(™nc]:=Ȇp&(ˆ1cÇÓÿù>4jÀ¡#GY±j5—Ã#¨S»¶|Bˆâ^tþ;ƒg³5ú«>ÂjEAQ@ËÕåÕ´ž™­"Ī èôè±bU54E‡OËVÔ2Jyž|c[ßþœ‰§øqêÿø1תtàÖd4­½:3àñ9üÓE6MîÇÎ96˜SÒ0cK­žÏÒÖ 0Ïîé*ÿu#§‚“5;£s·@ qgù{Åf…%b28S©N;F¾ß—Zz0Ý`YîËŸ¼WCùÜŠ×JïüAB!î Eù–)̃†™™™Lÿh£ÞKÐÙ`¦Íœ}]šZ5kðñ„QU«|Bˆ|*vcòç•X·ú;~Ûyœóщd¨F=½©PŸªèQmjñ`§&l;ÄŘÌ:{<*úÓ¸c/^ê×§+‘Û€ÞÌü¼*߯ú‰¿öž"46‹Þ þ¶h€‡ªâH³×>a†û,^¿—31f*4 mÁ¼öt­âÆEq£íÛ³ù¤ÒR¾Ú¸—ÓáI$éñò¯…¯Ñ„†Eq§ã¨Y|\ñKVüy€³Q‰¤Ø•ÃßMOšRŽno¿ËÅYKØpøñqà]Ë“•»r'ïàá#yþî×ç9|A*±(ŠÒ‰ânæYìÞ‰¦i˜L¦œû .Îά^þŠ¢‘‘QbmM üÞ¾×_ŠÑhdÃÆßYúuÉÿìph}'":Ÿ › ¿ñýÉ Œ˜9Ÿ†ûw°uÓF~;’ÌíÔ}r"ñTÅÆæê -Iɤ诡§ºÔ¦E»Ö´¬í—«'å+˜Po³'´.#øL[| æ ÔVŽñ[Hîaû¤æŠÂYõíw¬\ómÙkoËG'DÙR”9€ííí BwŒÎ£!ÏŒhÈ3# JàE«A“i5¨ å ]¹¡¹ž>bo§Üiü²|CòÞaÁ­õk,V}!ó½úMèC›Á“h38Ÿe ²è‡ùnªí –ŸG§ðç£yš»Ô|a1ÿæ¹OâHëÑß³]ªŽBˆR¢0%iš†^¯°hþ<~ݰ‘?6mâÜùôzþÕüy¤ËCtíòÉÉI¨ª<ä$„(˜Þ3ž¯Òóõ‚R´ã¥±íx©yÙ”oAß-è{ÃzÑ¢ß8Zô+`¹±ïoø›÷s½T}àb¶ ,bƒ7-ž‡Ïßh£+ÐnàxÚåwYáӆצµáµ»üEEGséòe<==iÞ´ m[·¦SÇRyÅ=նɶgÛ?…JV*÷5¿¯¦idffJE(¢£cˆŽŽaÁ¢Åwu;tæÓl8Ãõ:ÓµS[:~šç~̈‡¸pËÏ>ähÍ5â‡s{|4€Vû¾`é÷gŠq£ñ{sésûg€\;¦C§ÓcÐÉõ‰(š²ü QæÈÀB!„¢¤ö&©Åb!..އêÌSO>јõ$½Ùl"--„„xáB!îzŽ•_-¥šŸŸ†¸§Û6/^”wÍè±ãJÑÖdx|ßßÀêu¯0mú“tõÝË¢ËYãeètÅ;#}fõyÜæWæ~³—V'òëþt;ë5D„L{Ö²°î°^*œ(”²üÐÉG(„B!„²n’öGUU’““ˆŒŒ ,ì"aa‰ŒŒ$99UU‹”WöBˆÒÅÓÓS‚¿â¾i۔ą5ýãÉ̘òÑ9.ôFlmm±³»úck›µ¸4¥CÇÚxÚb°õ¤B€7åL‘D¥è+±1‰ØÔnIw=¶¶ÅÓ¿PI¨kîX o*µëÍã~¹{®ßþz ¿óãvoZ¼ô2=j:ãìæG`Ž4×_=N¾2ƒO‡ÔÂG_ޚΜ~~¸ëóà˜iÌ|º¼TÒûLYþB)ë×µLÖ«Y×2º¬k%ëZFQtwíZæÄžT.e#th×6Ïß ‰‰œ8y €ºujãæš÷úäßmÛå€.ær˜8¯ç†Meeœˆ(Õ.†„P·e·"½§ÔÎìææNÿþýhذ!GeÍš5DFFÞV¾ÇŽ£W¯ÞŒ5ŠÁƒ˜Îjµ2uêT–.]FJJ <ò3g~B¹rårÒ¨ªJ·n²cÇŽ|ó°µµåäÉLœ8‘~ø‘ÔÔTŒF#>>>´nÝšwß}‡êÕ«ç¤×4~ýúsäÈaöïßMÞ“ÎîÝ»éÖíQV®\É£v“¤¦¦òÅ’¥„GF°`Ñ"^2ggç{bÿEÁÆÆ¿êTð­„Åb¹í< vvvF¹Y,„BAv_«ÕJ­Zµ¨Y³æ-ç|b¿j)ÐöÑ>hš&í¢œkéi.„BiÛq'¹¹ºÒú–RwZÕjÔŠßÏÆD£”…¸'•ʰ»»Ÿ|2—œ×Ú¶mKƒ 9rññqEÎ3!!ùóç3oÞg¤¥¥Ý4ý§ŸÎeîܹLœ8___ÆÏàÁCX»ö§œÞ:Ž3¦“˜˜À_,âÀ,X0½^N§ÃÍÍ‹Ãhܸ1&L 33ƒààsÌž=›îÝŸbïÞ=8::YÁ¾gŸ}† 6°{÷nÚ·oŸg›~üñ'¼¼¼èرƒÔ\²zþ~¾x)Q9¯EEG³è˯xå¥Áw¬'pIÓëõ8::bggWl=€³ë§B!ŠFUUÌf³Ä=DSÕ¬a·%œU2WB!¤m#D‰’½w‡©b{zT fß±hÝÛÒ§OuÒÿ˜Á!«\ Š{S© ø...9r„%K– ( C† !00^Àœ9sŠœçŠ+øóÏ¿X¾ük† yé†i333Y´hƒaèЬY%mmíxþùç9xð M›6ÍIÛ AƒœßׯßÀÉ“'iÛ¶-ƒ!O#ÃÓÓ“-šЮ];ÜÜ\yñÅA„„„P¯^½œ<:w§'?üðCžpFF¿þú+Ý»wÇÁÁ«ÕʬY³Yºt)QQQÖg„ tèpØd2Q¯^}†7Þx€ 4kÖŒŸ~ú‘¶mÛ2fÌ;üþû‡YA÷'žx‚U«Vrôè1\\\6lo¾ù¿œf³™3f°fÍ·„‡‡Ó¨Q#¦M›ZâCÑf÷üÍüÍqOõÎØêõz9c݃TUåÌ™Ó\¾|‰ÌÌŒ[º1›Í„†^dü6')P!„(A𦢩VR"ÎáRQ†‡ºw>×+3-K/àœz.„B!m!ĽÆàX…Ú½Ð÷-{ qAÛ8’ÑÒ°Hш{µÎ—ÆjÔ¨ .$&&&ç÷ ШQã[Êsذa >“ÉtÓ´!!!DEEåéiÛ¦Mk ûöíË.*UU¹x1Œ5kÖP¹reüüªåYîèèH=X»v-S¦LÍéźcÇ¢¢¢èÓç9&OžÌÂ…Ÿ3a‡ԩS‡U«VÑ»÷3lÜøÛ-mŸ¦iìØ±ƒÀÀ@.\@||<ï¼ó.ï¿ÿ>ï¿?ž÷ߟßÿƒ &вe Z·n À‡Nà‡~`Ú´©T¨P‘O>™AŸ>ϳoß%|½vØçüDEGóù’/ï©á Å½)(è ))É´nÝ;;»[:§dÏüû6)O!„(qšréÞûLÑÈ™ø¾/é%#„BiÛ!î=º3ß0sô7Râþ©ó¥q£²‚žVëÕ›kÙ¿ët·vc¦(ÃÝFGGàåå•óšDDDÜÒú×­[‡««®®nÔ«W-[¶0cÆô|‡)îׯ/qqqlÞüwÎkßÿ=õë×§aÆ$$$°hÑbFÍСCiß¾=Ÿ}öµk×fîÜy·Uö5jTÏéùûÒK/a0xñÅéС|ð>ìܹ €øøx–.]Ê„ òÔSOÑ¢EsfÍšELL ;wî,±úñå×+nüÍÍW߬”£\”j—.]¤~ý·üBqhWú‹Š{å#ÍùŸ°±±Áb±dõо ?‹é]/„BiÛ!„·«TO:dÈ\\\qqqeÈ!9r¤Lt§Nصk'»wïbÆõôìÙ“~ýú³yóæëÒÒ¤IcV¯^¦i$$$°aÃoôë×½^Ï™3A¤§§Ó¶mÛœ÷ Z·nÍ‘#G°XŠgЂŠ+ššJzzzÎ:¼½½ILL (è,iii¼öÚ0<=ËáéYކ a2™ˆˆˆ,±²¼v±ÐiC/^”£\”j™™™üB!D©àããËåËadd¤ßñ¤é\¾†Oyù „B!m!„â6JóƵhÑß}÷-Íš5²Àï¼3†7ß|“eË–1yòdÞzëMâââ˜>} ¸¹¹Ñ¬Y3¼½½o¹Ü>úh2žžž|óÍ ¦L™‚‹‹ =z<ÅsÏ=W"`M+]!yROÜ=Ö [XuØ“.¾ÿàéû«#-»S÷hkC({`Å@Ãît¼Â÷gÍù§ÑÙ1 OEÚ„†3|[&Å@M'êžC)ûtíöÊa*Ê€Û”cÙŽ)!„B!„BqÏ0Üo;lccChè…<¯yzzrüø±<¯éõzÆǸqã ÷´iS¯{MQæÏŸŸ§oaUªT©À¦z½žÑ£G1zô¨ßÿÐCqìØÕý²³³cÆŒé̘1=ßô;wîÈów÷î݉ížgÛ¶mÍ“Æh42fÌhÆŒ}G>?EQd`!HcÇWŸ°(í<Ü&Žu_ÿ ½ïnØÑÇ•Z»ÓÊ׈«^#9Ùĉӱ,Ø“JüŒÎ*ž|ºC+"µ¦2wé%þ(Âô?6Õ˳ª›[Ö]`~èµýnª4«Ì§h|µü"ë’Šq_4•Øa)Öbèí«§]ת¼UÓ€¬•¤äLN‡¦ðçáDöÆ«¥l{…¸3z5Ëšb£lõ<ÒßÁ6q¼ÿs,'3s/rdÄK(·ë<ï.­£#(xÕðaJWgw†0ò€¹ ŽâP¶Y­V)!„B!„âVªÀÕ >[è´Bqß2âïIÔTÛc‹9RŸ»¸9н3¯w÷¡IR"ßn‰!4\Ýí¨¡YI½Ówõ5+;þ ã¼mVد/C|Óøü¯$´¬å—2Š–¥«££ÎH—6nl‹#$WtSqt¦S;lîö $ãk&6l¸È†bÊÎÑA><–÷w¦aÒëðp³£Y]Æöqå¯?ÂXl¹½Àm1o¯wJÙ ƒƒ¯'ïu13ê·$"ÊÐS®U½˜ÐÑ£EêÞÝ’žž.… „B!„BÜÃn)¬   Å;äæÔ©SHMM-TZGGGùô„÷-56”Ðpuu"!4Œ4îêöèËÙSßÖÌßÛ¢øéÒ•o†þÎHg¤y‹rÛ˜L\!žÄЕsç­ŽŽÄî‰à Šƒ Ž) =­·¥GO\bi¨B©rwÍo¿ý*… „B!„BÜÊVÐ º¤Hi !Ä}JKL`îfF·/Ïþ™ì=•ÄïG“8”˜Õ“S±sâ©úl¹Èê+½gƒÿ1ÐôOÚVˆbß•©ÙÓâÓÙ1«çèÑ#Ÿq¡¹—Â8'¯càô®æ¾Ò«ì’Š]Gêq[['z6°áü˜wØ„ ¹lÂÁ³*=›8±vc2©×|¿¹Øë°dX8x0žãu=é^%¹!~î4IMdt‰šB€ƒV´"¬#ú\<ßÏÚç3±q¯Jïެ »v;òÙ—›•kHáúѪ‰™œË„&î6ì 7ÌsX*ǬÞ4«¢gÃq+šÑžf•àÔ®4’ÑçÍØÖ‰žl¸ðßæ0åéÁ\\Û.DqË´”¥º§‘Åô}•™ÒÙ‡£Ñ—ù;­¨ç +»¶E±£Oy†wÒ“R͆ý›CÙV¸AyÐÙéqAå`X:§¢U ³Àk…*¼éAïÈÄ„T¶»èÙgûJ!!„Bˆ[¶ð«µRBQÊÝ8|ÀoîdüBˆû›FØÉHþKíºÔwg\#Nï çã½éd¸ÙâgÔáó?kºú.½LŽº|!R“LD¢ÃÅôn¶TÖ™Ùn¹í¡õîvø,ì3_ŠY5sô²…çýl©¨KæLîηŠg[HKVQ“ÓøéŒï5vaM¸™'ë9¼;³V…@8Úeí‹®0ëÈoG43'#,ØV±¥¼.™³7ÙYýMËÕZäòÒÝ$OÒSÙ¯ú;â|<‰Ì N46¤³òœíš°ÁÝŽ€+å`½Û.ÄíZ¹;–U{bËÜù7迾®X™—º¸qúWsѵ´¾ÜžÊ‚®®ø„F2=¨ðçZKx"?\tbHªÔ8Ȇ#‰ìж^wÌ+NÎ l¢gÓ¯ñ\°:©oB!„BˆûCܹh•L†;§£h*:MÅ^Ñ¡C¢Xi_ƒ5±v¤›uXudÖzkT*å›úñóÙpJŠEAQT@¡Ÿ³‡ªâ¦nÚø¦1àâžX!D™¥Y,œ<ÇÉÓ üÚ¬"3ZyóÔùPÖ(€fáŸM—øîš!–3Ò¬hù|i¨(èr¾gîÎóFzm 좡rø`"û¸ñB;+-Ô$&Ÿ¶ iÒÍàh«»íñ0E­_­7-×ÂѹÙ` áñ&TÅñ¦yþw&¡œiîBB GlÃbØÊõƒ(%¿íB—²ü½B5±aS Mžóbx£8âŠ|¬éñ¯h‹­I'Zº$ò[ö±¨*ètJþÍ~k&¿® akâΛ}Üyr×%Æý—Aî‘Ü]ýœiâ`CÓ^Õéž}îÑ)èÚø±Ú+œþ¤ÜÂþB!„BQúE§Y9¯hh6VÍŠYU8 *„«:4Eaof&iZ¦ #ôªŒÉΑƕ.qö¨'¦Ht(hŠ‚¢“ÎxBˆÂ) \ȨnîdrÞ¢T¸|`5ŽÞµq­ÔX CÜE*¡¡éD·r£‚ X/grÑꆿDœ2aÉïûä¬ñœ·¸Ñ°Š †ðÌëß_ÙyÕ«dD‘5<3:#õ+ÈŒÉäÒµSï*:œl!Óœ5œµŸÈ/ÜYÏș푵dío†ìmuèsaÖ‘ß>ëmi\QOzt—Õ‚Ë%{ä kÂMʵ0ôF:·rÅ/3•Ùg-XÔ›ç™|>‘=–ò©EFËæ [#v‰É쎹½:!DqYµ§ì³ÅœŽbYª¼é¯\wt¬¥Û:пƒ3É.òsT&êæXÚ<çÉÐz©|xÌŒ¦™Ù{ÎDŸf^¼­°9ZW\³Ï)®Žt«¤p!ÆL†ÞH ‡2¬$_sRP3-„åžX§#É æ43—RUéõ/„B!„¸‡©¤£#ܪ ÓfÑ¥x»¥?{Ãc©æbä&5Y³ë»/¥ãh“¢ÒÝù2åµX@OÎÈxš\= ! §à! ó_ó’„}…(U–ÔH|ýš–Mпs©Ô¸Ž~R6¢d¿L¬VRìéÑÁo{šÉ¥Ȗ¬‹acRVC÷ȶ0&¥{ñ|]/Þk©“…óA1ìx!„BqË:vë…³[9t:=ŠN‡N§»ò¯EQ²~Wt (Y+º¬Þ«Š‚‚r¥_YVÛ»¤Ûà'öl¤²Ÿß]-¯SÇÂQ­Vž°Í¤¹• qvèu:>jîCÓVþT­W{A‘Äî;‡þàql4íÿìÝw|uþÇñ×̶ôNOB ¤wDÁvØ+6Ä®xzŠõŠg9ýÙOE¶³+öHïMzï-!=[f~ì&ÙN‚ïçƒ%[fgg>3;[Þûý~qcã0†Ó0° “€àêúz™Š:Œâí¿šl4;ÿîPîœâ:¨ž™˱æê+†ñú[o5˳iýzÚ÷R«ûTù³Ž`‹ßP»ß2Í’ƒ·m„~tb¨5°Èáf[~r÷¬#gÇ rv®À×€]+¾§Ûð÷BOOËïǶ¼øw-!Æ€o÷ï¬Ý¹’´—âŽJR!EDDDDDDDDDþP~2/àdz¶AIN›Ïò (¹>>Ÿ#/Ol"÷Î^Cra€¾Ž'®(—Àºôr{ñ˜.vX ‹ü‡`‰<´ìßø9·1Áõ Öîbý‹ßònDëwdá2lbœ;œ‰ØÒ›v À*bÒòEthÅ÷›rYSä¢]³ƒ[ËÝ›{ìaþskYáYÊž‘8>êw¾È+mi;èÑ9~þ²»¦gšsÛ ¾ÿ2–°ÛÑ„v—ÜÈõ[Ð,rìÁ0m~Zßþ)Onã¢ç6„†}3ñœýw|ž3F/9àå¶õÉ8ï®ÔšŒ¸lv/ûÆÂO;+Φ=ú"þFN·AôhâÀ·i*_ûŒ™m¯æšS:Ð6¥ˆ}‹>füØ/™±ÏªM3º]zWöoNšk›¦¿ÃØ·f³¢ÀDu½Š—ö¥{Z$®Ì lúm¼»–L…çǤº—m\U ÁV¾¶lÜNÊ~ÿŠz¶`å/Ï…ƒ§wÂéò`Û6‚½øòv‘Òš‚œíL~û~ú|9'j > vçÒ,¹™»÷é&`:سm93·®&¦0½T¦&§°cÓjz5%ç —Ç×áDN5浕œÎÙüf\È€Î/ñÅ´ÒØ6ÓéÜ1›_¹‹×ö$‘záý<:òÖÝ:‘_ H¿öqkõ />öÌÌO£íy#¹ËÜÈïÀ’i‹Ùw}/z¹Öñ‹Ï£!Ýz$±úç…@ùîœm‡ ÇCDøu¦§VÅvå“<Ñöc^|â9-jMá#¹ëŸ>öÝùs½å‡>´Ít:·û’ÿ޽“q™õɸü<0¦7'~ù2/Ž~‰uή y;÷]¾Œs^ÜF<-¯ͨÈ'yäžE¬rufð-wóðå·1tÜüÉpóíˆÿwÍÉ"?©%mÜ;اð÷˜V—CàŠ»€uël—¹*Ôò×0(é÷9tÞP,rXäìZ/‘‰Á1Þ ÷âóyÀ†3"ø„õî#¹a{âØ¾{.¹[——”Ž?{†åÃ0€Ó—CdD{7ΧI'À""""""""""‡Ó1–Lv©O¡åÅá ß9ˆLŽ„Bƒú]šiR°mÙ;³ˆJLà8Ã`ý² ü8'‡|GW6HæäöIøòòùé –&šöý»¼h³½&ß f-¿‰sút vÚê°pÙÇöÉŸðÓ76ûXüÞ7ü|Roº¥Nà§í'qîñÛ˜úð;|·Î,eÚ›_3õ„ãp,šÂ޳éÛz¿,—p<ÓaÊ<‹Šà‚N÷òòÄŠ–µ¡sØ“9ÐV¦<8‘ÖGsøî…·hñü0.éù1s«hÜaÛ¦|ÊÏkƒë0ãߙ߿¾œÎâ]n` ï|y.Ã/êl—|—õŸÏ¤Ûf°4ËÌáÓ0ô¶þ0îcü‰MHwî`Þò lËuBî"fi÷þS¨«!°³ê›C£þƒ^lìâaàXÀvñàí"rHíÙ8Ø``Ù G8"‚Áo(¶1À P˜¹Ž"+Š}رüGöX:…ÃíÀ db˜&†aâðf³{ãlšt:C9ŒÎŠ °:ßðeé6IjÕ„ö’œCf¡E¬ÃGlÓdšwiÂŽõ{ÈÛ›K·³{Òî¸Ì™¼ˆå[·Ð,)š5{s >ဗ%ÙŸ{ä³ü "[Xq`ìX4™ïn<ž~-ü­¦0õåýÖ’kû&Ö˜½éÜÚÇ—‹ƒÝ=[î´kº‹õße‘½\[V±Ô!½»¹™ø£UáT…ì[ú,ýЉŸ`ÌãgsZÃY¼²Õ£ýUWÃ_¨¶ èýŽ%Áph<`Û( „ C!°üùTâªéÖÏ]G£Mðåí"3×ÏîÌ}ìݾž‚Ý+‰¶w’€· õ’‰NhDΦ¹¸"1#KàŽMLÇ´dwV=r÷l#×Ášõ›i’ädëR¨×¬WµËÑñî<í Þ4œÖÓä®nÈþ…‘Ã>¦`ÈXî<§1&Ø»†ù -8ŽqƒÇ#¢­Û6 {ùµÙõíHN¹òS² w‘vaGºwo‰‰%cNàþws¸ð±Gx ÑJƺ“ç0wÒtïÖo£ œ)­èÖ½;î°ùåo]ƼŠɱᄰyéèÌŒl@ìøðôgñx_OX;07Õ÷|3fÑÞlv¬ü™WG_JŸ wòÃôÑô‹* ™Kæµàžžùï2ó²w}ÈØ;ÇðcÓ«øûËgÑ>Éb׊YÌ.4ˆ6Ûdφm$¾žO®¿½s'ðÐðH›³˜û`¾Ÿô¥žH""uˆa˜¦Þ÷c%À"""""r¸`²»ÀÀ••OçXÓ$*6šVñIÌ™³ àñD’œœˆÛå!22ËEÀvÓ¼k7>›4‹ sÑ´aí›5áæÐr¢2¨Ëvæ<½ŒÂZ¦Ø[˜1}þ‹úÑ7b ?:ªœsß$þ7õBž»þÎ{îc~Ø™LÚ'ÒÓ±›)¡i…ÓùuáÜ1Ì eÝü5ÇyP5tîýЦžË³×_ÇÙÏ}ÂO»ÑjèÕ Í}›¿«õúÄ'%à6xíÚÇÍŽ¼ùôû¡Œ½ü®Þý ï-Σ(¹=cW1wmþ¸îœÔ-͋ױ!£ex IDAT7†-ë“âÝά\‡vòcT]¡ªØ ´õ¯¡È7t@(î:4¡ibÐgðÙØ¶]r¶‚… ».4—’VÇá3åà~"ò‡õðzàúá/ÝÀæÞ¯È _÷}QÃû4jæååKçåâ«\‰[û'uy©t²–.>¹÷&>©vñAcvM¸Œ6J¯nØ"Š©¯Œf*@Tg’·½Æ©]^ ÝÚ…ø™ÿ¢ÃqÿÚofѤÇÏäºî½÷»Þ¤Iý4¨ŸÜ8°‚eêL:ßróˆoK¯ŠëBûG®ìSËy5ìNrÑbÆ>¾8ìÊŸøf\øD³(">£ ÞOn¤Ý'‡f?²m oaW-ÑsRDä02Œ`8‡ƒ¹sçâõz9oè0œ.7F(6†E1J‚E£dÈÃ0JÞG†At\bÉð*FØ©ørð]·6äŠQò^½ôUZáålKBa¾i:‚[ICÚˆˆˆˆˆÈa°Ï2ÈöDd{ÉÍ·Ù±zÛwzÙ¶½Ì .ÑK‹t~#†{²ñZL›¶”5 WÒlÇF.éÙ§›Ë×øç nTð-O/vïw‹EÁ¬iÌ~"'txæVhÚÙ¬ÿw¹êz®¸ï4†6°ü—Ÿ™²-¼‘Q¿ÿ¶Œèû›³ý¿ Øk\Œ½Õãïã¡a×qŽáJÇ6χGü‰E¾àòΞ´€ó¯}w[Žá§—Àƒä³å­;¸ußõÜpÝs¼–lbe­cÕ'Ï0wmvT2éý®à²+ëÓ ÊKÁ–ùüúÌ›|‘íÔN~ ªëá/€qêÐvÙ. íÐ?ì`è s‹C\«ä²e[Áë­ÒëmÛ ]¿.ü–ÌBíýß°~ä•‹ˆÔäÝ–eS”ŸËò¿©""‡óˆkÛX–…ßïÇçóá÷ûé9ð/x"£1M£4È…²nI°kÿša狇T1 Là ‚Í2Apñï.Ëü³ø ½‚Ëù*»‰aš¥õ>Ê÷¿ ?¯Ù”ûŒf[Vh `[–e…þÈÉÚÍ/“>Ò. """"ìÄ!›ü1¥ibšfè¯#ô™Ç¬àóMèsÒ~Ÿi÷ûðe3'‘Ö¬Ù­—kõVæFãÊ¥m‡$vìÊ%µy2m{e0ù‡lY³“mc:=ìÉ$ÕS@ŸfQtN UF:ñ)ÉÄÔK‹•ÔŒ“®x¢Nì'ÞÆ×ñÄóo~–’Š”Më×Ó¾÷ZÝÇYÕ—"Å_0Á¯‚éÐõÁQ€MlÛ }ebca`‚i'°Í`÷СØ0Bm°ÐÈÂaIoxæk(©àl S_þ‹ˆnÅÝ?;NLÓÄívãt:p:]Án¡÷kÝ[®åoØ—å_³46(—Î×ûžDðÁmÌàeÃz‘Co‘7‹Dò}~–,ÜIR‚‡¦É,ûx>©Y^º%´4wÒ<-š´þõÈèÔŒ¸ú pDFãj˜m0Š °£’ÈÏ)¬#kmÛ³']W½È³9 EŽ„ªŸyûu]œÐ–~=b§ÂØ€ Á$x¯`+_Ûƒ¿^/¾WqÑeç‹ú‚© ÛÓÆ45Ö„ˆÈÁ(é6ØÄ¶mLÓ‰ÃéÄa¡÷À¥lxx[¦5oXÞ ¸8Æ0ö»/%Ý?+>ôÛSDDDDDäp²m76øŒ \,%­e ±k¶qQºIã~14IO¡Az›¶ÄÓ°9Fl2ÓÍ‚5ëYüÝ4z¶J§iýD"ýàóÔu6›Ñ¯_=6þ¼€Ý¶>w‰ N~ñQÚ¥X0õ5ì°X6ì²a„g³eG+nŒaƒe…&¶À¥Ç¡™Fi7ÐûçR²:&ˆˆÔä$†mƒ„?k7³>ø€eé3¼_2¦öùƒ„¼%Ý&˜fq'faÝ5—v5 [úV2æo™@¸\ë_uÿ,"""""RYf2ذöàŒŒÁtXtKw‘‘æ"©q É[Ó¸1ÞèdVmÊdEæF–nË$?`Pàõ±kýNN¨×kW&-c£ëÄ:ûÄ)é3˜>ׇ…[;ÈPMÛûððw¿VÀ¡ïš‚¡oÙ»››¡îͰV¿¥-~‹».£´A±ˆˆÔ„mƒma˜Æx “'Ndþùg0¼_ò~uÉgËâùlŠéBïÑúM‘ˆ6†>†¬YÚµpÉßýZW06ðþׇú¦²¿""""""R—>8àväp€N,Y¸†Å‹vðÎ:5‰$9%›å{_´ŒÍYn² ‰kE·ÞÒ[xhÕº©MR±\Qàtðëæ}ub•Ý[^åþK…¿"GL%pe­€ ìâØ® 6 lÛ }UUÜâ×.8¬ è’ ʼnrX’¬ÞŸEDj(4®ú‘ol“óûg<÷Âûü´x™> [Ðå/×sÿu}Hù£Ï¿Œ7î½——M¤—`9ŒL#ØØ4%oŽKZåû1[øÛá·UÖê—Ò6Å ‚EDDDDDê Žm€ÁÎK¨ß ˆc5°: pQ6 ¢ŠhÀ¶Ìž¦«|"r€Jà²Ý@‡ ‹[îåBà⦿&6vè¢ìî¹xÌ_lŠÏÛ¥Ao(Ÿ“ˆˆÔ€ü¥ŽiÙ#§ù#ß6†é Ïäú{o¢EœEæ†e,5cˆUßÌ"r víµ6JCàÒ_;–¶ì uã\®kçrá/•vù¼ÿc‹ˆˆˆˆˆˆˆˆì¯ê.  J».þß00l»’¸´!¯ìð™²ýFÛaã‡\®'hµ©‘P `ŽpÐþÕó˜—Ó€³ÆŒbx×ÐKKÿ9«ÌDÛ™òÚs¼òÍ\Ví Ôãv.jƒaí䇧æÕßÖ°yGDQ¿Í®¸ýN.ë|½ðnä»—žæ¥¯ç±)/‚&[áÉWåKÅÂg/ ë³nNý=OžÖnfLx†ç?žÆÊ½É­ú1ôæÛ¸ªgŠÆ‘Z3LG¨°ö¦¸´ h(;fpèL08 xˆ¿Fø(¿jø+""""""""µRE\Ü¿Á†½v¹ö 84mqІaçTÜtx·ÏPî -}¿%"RCvð‡5æîÚÑ(TÇn¦3mÒ¨ÜðÌa$w}׈«ïxœûêg3ãõ'xü®çhüÁý ˆÌaͼìi9‚Gïk‹»`“ß|‘'ïuÓìÃûé™Ëô§oçþo¢9kăŒjádçÂ/·Ä®"vÒfØ9³&&1 =@‹ÇŽäÖ÷ †Üü #[جþêež¿ã _Ç_Ûk\©`÷Í¡ñͰֿ¡÷Äáï+j\— ý‚`½K‘š)—ﺤ pÉEl#,.Ó 7|¢ð™†.†\òÍWãýÚjý+"R%݉AfêÚ@ëkÞâ¡Í0ž]ÓÈ[3œ oOeøèÁÄh—‘ÚƒM3x*î:ü=0ûu]¦ÅoñûfÊv]ò¼ì[òýÃ_uÿ,""""""""•)׸²¸¤+h#ØÖ7þÇf¿Œ7t6ôÚû?@éÇÅvùÛED¤Zv¨ hÃ<Ò»i~Æß™xÒµ,üå>ûì-nú®Í“×t&jÃJVä³åá³èõHñ},ü>Ïî¼ ;þ7§ÑÄØGV¶E`ÓjÖù0ø¸†ÕMs`ÃrVÖgP·ÒPG=ºÖcìo¿³!0˜íW"Rs†ì…¡¤ èr=۔Ƿ|ð[ü~»lX\¶ëg…¿""""""""RsΚM¶_ ¡ËÁëíP \QºHÙìlÂûŽ6Ԉȱ‹G[?JÂ#¢!]þr]þr1—¼1’«_ÃÛǿŠf2§ÿëY®mž°šD¥Äc°·ü¼X¬°µ=è^"ÔË„ˆâã^h<_ÌðÞöl÷»®\ð[z>ìüWDDDDDDDDD…pùVÀCùÖÀ%Apñ´%ízƒ]F—›Wxàõþ,"rlƒ£/ ˆ$£OWŒ}[8ºdÐÂý>+7Z¤þ¥eùq{­ªçæhÚŽÖž˜5s=¾NUŒû[ü2ã!"rrʶ.ÎçCæÍÛ‚Õ©i°p`sì"¢U[šªõ¯ˆÔRq\Wt<6ÂBÝ2]B—½\æg‘•„¿jý+""""""""Õ©´pU!0ÿ E»áAp¨uoIlšFx««â`¸Ücªe–ˆHíG~ `ïâwxè‹:÷hCjbÖ¾õLyÿSÖGtâ²6NŒ„vöþúÖß¹Ïq5çtn€»pkö¥rÎ鈮nãrÕÐæ\÷Æ(îá:.ìÚwÁ"ÖTrG:í2ܼûí&v¼€Vö62rZ§ã¹ò¢¦\÷ú?x(êÎl«¾ËøµÍ¹üž¡ñ-v|ýw®xrg>õ·vvV¾y7¼ãáÆWžåÒ¦&Ç´'®bÔoyð¿38A¡ŒÈŸól†Œ Þåý¡êà·’÷Ê EDDDDDDD¤ªìºÒ* ‚æ²Ã'·Kn´©¤ZIP,""5üÑÍ‘ l|îxâ2âÍ'ßfkVfT2MÛŸÀ]ÏŒà‚F&MÏ۞㙄ÿ2ö‹§¸óÕ<ˆi@ë“næ¤!ÕÀà¡ÓÏòbü ¼øÑ3Ü>.ËOƒ´. nSþuň甿ÝÍܿċ£~ÁFÿ;pj§æ7âižx–ç'<ÀM™Üª×>5’«;x*xa ¿h—ÿ™’Í!è–ZDê2Ã`¿ÀeÞ1—¿´_À[“à·ø}¹ˆˆˆˆˆˆˆˆHM§Qí7ו¹mWsÖ®tòÞ ""ÕŸmË"/'“?|¢‚ˆˆüÁúœ|ѱI¦Y>¤-ÿë”òWUÞ!x­Â_)ó™Ìý³ƒ?N²Á¶­àí¶e[ØVð²e°- ˲Bädíæ—I© """"rÀNr!± )˜¦Ã41M3ô×üq¬ib†zI þXÖ ¶ý2Œ`ÿGaCÞîÏ;ËfN"­Y3m4©Ó6­_OûÞCjugM&*>WÚšü†Ÿ-mõ»ÿä¥*m,""5;H«‘#z ®úXlTy…Qí{p‘ÚpÖf⊻„.¹5ì¬]ñ-v5÷‘`èX*"rÁ¡ß°WôÖîý¯Â_9PfmïPÑgLUñÉ?¡“N:é¤Ó¡:‰ˆÈ‘Sæ˜ö~·–횽ϩ{/ >‹çdØÀKö.~{ã žý~+Ö1YŸ#¼~»}jýxÙ,ùúMÞúmû±¹=l?S§ìäé%¾Ã»Žv€ùó3y]àÔ¬&ûë½O‰ˆˆˆÈQË<˜;Ü—TJltÒI'”‹ˆÔuw,Vè+rl°ó×ðÅ3£¸ââ rî%\ñì òކ ì`ÆgŸ3y}~…aPørŸvÖùüõ½øÖ}Äm—ßÈ3³ó±­}¬˜9“%; ŽÍ0éH¯_UÛÇ¿œ×o¹‚kÇ-¡¨Ö;d۟üûÍ×ÎdÁ7_ðóêœc7´,_“Ïâ}öá]GÛÇôyûøu·UëÇ9àãEMö×jžó""""òçámÿ˜Ù‡u¹sÏッgÚåŽ3³î{Œ³ŽÞˆÜ|‹@£Æ ˆÄ°Ø“UÄÌ%ûxàí\† iÈmŽƒëvîXTW""""Rç车ˆÈ1bëÖ­*‚ˆˆˆã\¤´è@ 3\°±­:v¤µ°3™ýÎc<4u9ëwdãu%ÑÄãÜ?8-“çw°=Ç‹3.•Χ]É­—u'Åì=LyõY&ÎÞȶ=ÙAJ‹žœÝ œÛ6Ã.`ÍwãyáÃ鬨Uˆ3¾1}¯ü'£Nªa{)òØúÉ(NÿÀA›«žçÙ ›`úw2óƒ7x÷çŬÛc‘Ñ›‹®¿Ž3[EaØ™Ì~çeÞÙYÌ©èúÁIUÍÀ»•_ßÇÛ?/aK¾‡†mšãεqUTÊJ–ûéãçrψIÿ×8FWÁýª\§*êTf&›«Ü•Ô¦&5(Y¿=ü6þy&ÎZÏæ]Ùx͵íÍÙîàìvq•´n®f¹¬L~ò:¯}=‡5{D¦dpîÿæòv®ªkSÛícmàí‘wñS÷Ѽze+Õí£å7ËÆßÈiãÜ?êmþÙ?¸Ž»~šk¿ÝÍÎB'ÉûÏ£¦õ 1#]thA$@zƒ:ÆÒí«­<ñýn:6jÀ)Ñ€`î¬=Œ_TÀš|HªÅÙÇ'qqš›9?lâþ­±¼8,‘ ðä«;ØÒ7';[¢ï[ºƒK~6¸çº$ÓwóÎ:/[s- 1H©Å…'&s~#³âíZåãm^´“L+`[¡3ÒE—ŽIÜÞ/’”âú}üòÛ&ü^ȯI£FnÜ…„m;‹Õ‹÷òü¬<–çØ¸"]ô=¾÷·wî·_Vq¼°ö2ï£×x}Ò\ÖfARóœqå5 íœXy^“}ê@ŽCƒ“Ô€ˆˆˆH§XDäѸqcADDDþÜìlVÌšÍöÆ—rÏM퉵r1'``Øî4®½ë\R¢mv/ú”—Þz†W›åÞþÑv.–,#³éåÜ{K®ÂmÌüß›¼òØë¤¾t Ýw|ÎÿCÊe#ù¿nIØY[È«—˜4<õvþ}N:&žÄ˜²ä͇xdr}†^·$ç2÷ƒ—yé?¯ÑðÅ[èQɲڛ*Y‡jæ™ÏÜq2æ×hNv7¥;سìG&®¨$`¬l¹ ª*pÕËÐ}guu*VÝöÈ>°„÷£kç²nÑv§_ƨ›Zâ.ÚÉÂIïóÊ?ב7æQ.kYQUªY.¼¬˜ø0ÿüÔÏ€ËnáÊV1øöfÓÐYmmlû”]ŸªöÑž‘å¿îiyþ}Ü}R &&ÑõÜ%·xÒú1üìN$»™ùÑaó¨E}+Ý¥œœÜ/ŽßÊbÒ*?'wq°|Úvþ1¨Ç ɰnY&ã?ÙNÑÐÆ\ÕРmZîeE¬(„Œ(ì-ä÷B›]ÛŠðvŽÂƒÍÊ­EÐ0Nn‹¯7²7%‘ûOöàöù˜1{/c¿4H»2…^å¾å²ù½šÇHlËõ§Ç‘âÝ÷ñ´]Œ­ŸÊß[™XÌùu£—œÖ¯>·$Ãî­¹¼³­4¶vgóøOù¤ô­ÇÓÍœØù^òb+ëF½¢ã…—åo?Ä_À‰WÞÎué6ë~z—×zïc1¼•»‚}¢&ûÔ‡ôj""""Rç)‘cˆITÓÎôîÜªÌØ©ÑͺÒ/t¾uËÖO¹ƒïVl&пM胱AdZGztn…ƒNtNÞɼ»~aöš]­,öÙ1tëØ‰v-#€–û=¦3¾!M›6-yL;ç7þ÷ͺß4šaýƒ-N3þº“9׿Ïäe7Ò³[%ËjU|½3³Êùõh=“Ï~ÚKÛa2ò¬†Á§C k~˜ËÒJkUÁrWW· ]ÝÕÕ©ôq«ßµ¯AÏ®r•Ö‰^]ƒóèÞ­Ü>ŠO>Çywö&²¶Ë•7‹¾ØH³‹ŸáÎóRËì_‡gû”_¾ÊöÑžËìIlBó¦ Küо›Ñ›zkÒ1q³ï ΣGÓÚÖ·’g`‚‡ ÌÞëÇ_XÄ |´èÝ„;»¸0.M\äïÞÂsó¹èŒh¢›DÒŽ½,ÜjqF†Éž-…lw›°µUVñ²`‹E³ö‘$6QIôJ÷à ‚.1~æ¼—ËÌ6½öûM¬]˜_ýãÑ)‘ôݧM}ëVnáÛm>­<8 òùd™ŸvýR¹£k¨EoªÉš¥ù,).mA€}˜tO¤}}pW¹Ë=ïr§òáW›i~ñ3Ü~f*&йCc 6Ü·Ïá‚Qýˆ®àùXÝ>Uís¦[åÇL©Û‹ˆˆˆˆˆÈ1ÎǶ0þÓX¶y/E®X\m}•ÞÃlЈfûr,œ=þÂÐ.3x執²ò„Ó8ëô“éß2¾Ê°$°eë Øñì5œù\ñµ¿‰go>v-× ºùù·®g£?…þíê¶Ö{Õ-ƒcPMëTûíQ³šÆW½îîôè”ÈG‹V³%ЛŒZ.—ÓjV¦Ð¯c£rët$¶Oø>ÊFwfýÒyt}+Úf™E¬ñ;é—ê,íÆØtÒ9ÕÁ„µEl¶¢iIïú6môâËp³h£¶Ýˆ˜›Ë‚½ÐÞQÈÂl½š;1)¿˜ñNöàã›6ÛÖdñÊì<–í Pè4q{ÁÑ(ø, dyÙh9ÐØQéú;ÇrIz/ýo3+ÚÆrNçXÔwÔx«6¯fmQpß*YNGcŽëÌ[sV³9Ð6ûß§ûÔ¡>‰ˆˆˆHÝ¡XDDDDDDŽiÖ†OùÏã_bžvwßA¢±•¯žþ?¦UqÃtàÀ²WSÎ}à%z-ø‰Ï?ýœ'oÿ„O¯ø7^”§Ò9Ø`&1xä\’™D&ÆbU˵¨f~›Œà4‡5Ñ©f\ñ5ªÓlšÕ´z†iRY‘ª_.»ŠòþñÛ§Ì>ZîÆ™ÇÁ×ÀÊ,bU4Irâ «l8éÙÂ͸¥ù¬*´˜½ÍIÏ㣉ڔÅ}œlæ³66’[’+^Ã0p–]é–©zywï㡯²1;%3j›D||ñí®rûc•óq¸9ï¼TzmÈåÓ¹Ù<þÎ>>îßÇ{yª8FÔn9+ÞÈÕíS‡ú8$""Rs>ŸOEøp¹\*ÂQJ°ˆˆˆˆˆˆÓ¼ëW³ÞnÏ­— ¦k¬vMbÍÚÍĈ q×ÓÑu0Ç¿“»¿øžeçeе’Ù87£©ó ÖlµhtbÓò¾­Ú=|uó³S[ÒÒóóçoÂß¶Ù¡ý°oÕpªª“óà·G¿ÊõØÆÒß÷âIoF#(»~Õ-—£q3š¹¾bñ’mÚ–íúˆnŸrÜxÜ—›GmÚt}>¾›–ÍZO$÷´râtxhéÌfñf?V£`ÌX~m QÏCj¨¼iQ4Ÿ‘ËóÌŠä¢x'ÑÍݼ¼:—¯L/õ3’Ȩ<»/§8ªw$VÿøÞÝE¬³#Ù7†n€m’V—Ð<æoðáo䮢.&MšÆqsÓNøu+w,ÈaiwÝj°©-iéù’ÅK¶cµm\ÎÀV/ÝCDó–¤V°¿:j°Oê㈈Hm(9²‹ˆˆˆˆˆÈ1͕ڔ&öW|ýþ/¤ŸN¼c/ÛójÞÞÎÚ6›¯Û4kžB„o7 7åAl±U4‰4âzsþ©qÿÿžàQó"Nk_Wán6ä6äÔAm‰ªå:T;¿˜Þ\tfwøÿáNïXwÁr¶D“S3–„X›‹~cÎÖFôj\õ2Dl¯Yt{T[ƒrÛ#À2±ññ´KM¿¾ÇÄu øË5=‚ã©î·~Ý«Y.#®çŸö÷½÷ÛC9¹m޼6èO¿æG`ûTÆÑ„Œæ.>ýõ|ÖfÍíì‹ïÍ ­u}ÁÊ÷±hS!nË"3³ˆKsøy¯ƒÓ‡¤pR4D1´‹‹;gîä)W"''úe™¼»ÛŃ£JƵ5£T/‹q³ý¤õhLsÌ–Q4ÿ-“÷m— t×ì ,ÃABìØ˜Çìö.z'Vÿø$7©dóÕÌ\êµvoúÙ^6ˈ(.îââŽY;yˆÎlâÄí+d³7¬Yù|±š§8‰°ü,ØkA„£ÊcD™ÅŽéÅ…g¤rÏOðLÄåœÔÔfÝï0qcçßÔ³ÂýµWãê÷©9ÙÙ3xêöçYÖûþ{Cç ÆÊ‘º@°ˆˆˆˆˆˆÓ-Ïçž›öòâGãù×çyXÎb›Ð¦Qtºµõg­gêÇ_ðʶl|ŽfôbÄÈóhé ŠtQt¾æAþ7·ÏÃó!*…æ†3`µ€«ŸŸ›¶—ÿ›Ñ±o2áëñ<ô^.W,õšt ZÔ;kÔgð%ç0ý…ïxõÛ^t¿ºm•ËପN‡d{Ô¶¦ngó?ø/wy‰iÒ‰³î¹ž+;ET²~Õ-W$Ç]õ ƾÁ›ßŒåÁ·‹pħrü5éÛ<ùß>•n·X^5‚ÅO¿Å›£gˆjDa­Øú`÷±²µŽ4qlÌâþ³0& ±.Ú6ç¡Óãè›h”L×¾_CqîaÜì]|Ÿ‰õ"¹ìÜd.i¶Ö†‹bï g IDATÁ"xcG€­ÝÁVËñÑœØ “•V §Ö«i’êää>ñLû1›—GÓc §ÚÇwÔçÞÁžŸ½‡¿Ï³°&±Ñ.Ú&˜¡íbЮ_CÆDdòú¢½<0#@Àá ^bÇ'§ñå{™2;›±ûøL“F ¢øë©ñÁVË5â¡Ý°ò ç5^ÿè)î߉ͻsé¿®ehkw¥ÏÇê÷©;ÙUvw.""""uqêÐzO'"RGÙ¶mYäfg2ý»âÍgËâùlŠéBïчö .‘#¨ï©—ˆaš†Žnrx_óCçBÿl°mllÛ ÞnÛX¶…m/[VÛ²°,+ô7@NÖn~™ô‘ *Ç>ko¼‹ŸºæÕ+[Õ¸d©Þ‰C.$6!Ót`˜&¦i†þ:0 #xÞ0Á0‚— à x#4V}èg6‡ùsÔ²™“HkÖLMDê´Më×Ó¾÷ZÝG-€EDä°Éùý3ž{á}~Z¼‰LŸ‹„†-èò—ë¹ÿº>¤–ñƽ÷²â²‰ôR,""""""""""rØ(‘ƒfgþÈ÷azÃ3¹þÞ›hg‘¹aKÍbM  ‰ˆˆˆˆ+?µNE¨¡Ÿîh®"ˆˆˆˆÈN°ˆˆ4ÿêyÌËiÀYcF1¼k襥ÿ‰œUv*>{]Ÿpsòèïyò7>¾ŸÛ^žÉ–}^œ Méyî-üã†~Ô7kS_}‚WX̪­Yx]) º{?‰ú§´$ɱ“­¹ª§ˆˆˆˆˆˆˆˆˆˆÈR,""ÉÆçŽ'.ó'Þ|òm¶faF%Ó´ý ÜõÌ.hdñœò·»™ûï—xqÔ/ø£ÓècN½ð þsÏn›ð4·½—KÀE|r:ScQ;`‘Ú3N:ÂVDDê&Û¶±-‹ÜìL¦÷¡ ""òë{êEÄÄ%b˜&†¡Ÿ®Èá}Í ý³Á¶±m°m+x»mcÙ¶¼lYl˲¬Ðß9Y»ùeÒG*¨ˆˆˆˆ°‡\HlB ¦éÀ0MLÓ ýu`Fð¼a‚a/&†Að2Á_ý??îÏQËfN"­Y3m4©Ó6­_OûÞCju,"""""""""""""rŒP,"""""""""""""rŒP,"""""""""""""rŒP,"""""""""""""rŒP,"""""""""""""rŒP,"""""""""""""rŒpª""dž­[·ª"""""""""r¶mã÷ûñ{½, +À²,lÛÀ0 LÓÄt8p88].œN'†a¨xª­j{ŒP,"rŒhܸ±Š """"""""ò'åóùðâóùp:8].<.¦Ãiš%A™mÛØ–E  PXP@ Àåtâöxp¹Ý*¦j«ÚÖq €EDDDDDDDDDDDê(¯×Ka~>‡¨˜˜*[E†áp`:¸€ˆÈHlÛÆëõRŸOaA‘‘ ÔT[Õ¶S,"""""""""""RÇXyyyØ–Edt4.—ë€çe„B8ǃÏ률 €ÂÂB¢££1ÕVµUmëÀ""""""""""""uˆ·¨ˆ‚ü|<‘‘x""8”£ ºÜn\n7EädgûOÔªRµUm €EDDDDDDDDDDDꈂü||^/1qq8c+GOd$—‹¼Ü\>‘ÑѪ­j«ÚÖ¦•""""""""""""G¿ü¼<ü>1ññ‡5D+æt:‰‹Çï÷“Ÿ—§Úª¶ªm¡XDDDDŽMv«¦Nâû¥Yت†ˆˆˆˆˆˆÔqùùøbââ0 ã{\Ã0ˆ‰‹#à÷SXP Úª¶ªm XDDþÜì|¶,úkóÊDþÅ<éiœûÌ<ŠT%‘£„—}ÛÖ±a·ÆÏïk×°%Û«XDDDDDDê4_Q>¯—ؘŒ?0D+f1±±áõzU[ÕVµ=Êi `9xö>¾û×åüóûÝY`º"IhМŽ}ÿÂð«. G=ÇÑ»ìþe¼qャ¸l"½ZDSò6ÄŒ¦~zSšÕѯ¥DþÐO{Y5w6óVnb美(¤Ñ¦{?z§e2û«¯ØÕí Ò“Ýú ù,úä5~vžÂ_ÏjƒK[CDDDDDDŽV @^~>±±±æ‘û¦Ê0MbbcÉÍÉÁápü!]ùª¶ª­j{`‹ˆÈ! ;kN×ðÒßúéËcϦ|þæ1}=ÏM¸›~±FÝZ%³y…‹µqEþ8E[˜úñÌÊŠ!£SNn‡ËŸÏžm›(ô›‡4ðõîYǪÜDÚ6MÀ¡Ê‹ˆˆˆˆˆÈQ,//ˆÈHÎ#é8"<òóòˆ‹SmU[Õö(¥XDD3¡)ëD@÷> léã‚ë?æÓY£ß |¦¾ú¯þ°˜U[³ðºRt÷xÆœ‘‚iífÆ„gxþãi¬ÜkܪCo¾«z¦[ßZ»øé¹G7yëwdQdÆ‘Öé.q—PÚB׿)¯=Ç+ßÌeÕÎIíNàª;nç¢v1ÖžŠÿT? Ÿ½€®Ï¸9yô÷U5;ŸŸ=ãoüÄÒí…¸Ó8áæ§øÏ™Ô‚X¤Fül™ñ³3“éwá¹ô©_Úþ6£MÇà™ÀÀbëä7xjrð­l«ÓoäìV^ÖÍø™«¶±{_!Gƒ.æŒ6•=–Mþ†Ùü¸±#-š&©â‹ˆˆˆˆˆÈQÊ[T„mYx""ŽšerGFRäõâózq¹Ýª­j«Ú…‹ˆÈa㈌"ÂðSä Y,<•Íi×óŸ{:ÈÁÑ4 “"É­ï ¹ùAF¶°YýÕË<Ǿ<Ž¿¶wÙ¬š=-n`ô¨¶x ·2ë¯ñÔßV‘óÊXnhã ˜ÿÂHîú®Wßñ8÷ÕÏfÆëOðø]ÏÑøƒûYÙ㯜´6†GÎl€‰ILCOkSõüûm›È?Ÿøú×ÿ›ñ}ëaíÝ@nƒd…¿"5åßÄâåÙDµ9‘îõ«ê|Ù¤~÷3ùKûX <±N ‹ík×±/¡C5&Â*ÄHŒÂ Wu‘:­° €Èèhަ¾õ 2*ŠÂüü:¤©¶ªí±L°ˆˆ:–¯×‹éÍeçúy|þâG¬òtá¢.Q%/³1-{2°gû’.WíìÉLøh­¯y‹†6ÃzvM#oÍp&¼=•ᣚ6¦ywŽï¼oß¾­0†_Ï;ïÌàò‡•ý+o~²‹~£ÆrãI @ÛQÛøíüñ|¿à.ô­øññÿD$§“Ñ2µ4°µÊ®š]Íüûxö’eÇÒ·[wŽk ´Ñþ R vî^öz RÔ«vì]gt")Éñ¥"ìàóÛ“œF‹´•>EDDDDDDêŸÏ†Ëå:ê–ÍårQu¶5¥j«Úë!°`9dŠ&?Ä ý ^0\$´ÀMcîâ‚Ff¥AL`ÃrVÖgP·°ðÕ‘F®õûÛïl ¦CE?s·¦_÷dÞœó;iµa%« òÙòðYôz¤x" ¿Ïij;/˜„@5ów 9Ÿ«{þÊ“·\ÆÒÓÎeè…gsR›D-*RS%OÒÃ÷ÛPkÛ^ÿpY%Ç£m¼øÌ÷€I½>—2¬O”¶ƒˆˆˆˆˆˆ5¼EEx<žºoëV­¸kämlß±÷?úK—ý^át‡ƒ¿Ý4‚¥¿ÿÎ÷?þT«ÇðxðhÖ0 °Ãîo&sú¿žåڶᱫITJ<{~«š¿;‘KŸùþ³¾âýw&òÀðwx÷¦gyùªvx´kˆTÿ|ŽI Þi³u×ü¤–7©f½nœwi[üØäþþ_îhÉy'¶Àƒ3*“"m9*ضÏë%*:ú€î¿rÕ*>ùüsn¾ñºvîÌ?|˜‹•›îþ{î¦O¯žœ4èDLÃäÛ~¨ñc¸= òóÿtµ-ær¹¸æÊá x<±±±äääðó¯“yíÍ·‚-aП½¶QQÁéçççÓ¶MiO‹‘‘‘˜†AÞAÔ¦®Ö¶¶44¡ˆˆº•¸T:tì@‡ÖÍI­Qø ަíhíÙɼy[J 61wÁ."Zµ¥ieMh­Í,\´OË ÒàHË …;“•-R›5£yÉ)1U,‰á!"rrªn%\£ù‘¤÷¾»Ÿ{›—.KdÉŸ³À§ýB¤fŸšRiÛ2ŠÜsXœ¨äùêÄ儢¢Úýt¤xbg4IõêQ¿^=’bœàŠ%¥^=ê×K!)ZíõEDDDDDäèá÷ûq:FízÊjݪUÉùIß~Çç_~…iš\9ì²rÓºÝnz÷ìüÈmXvíj†Ãá8¨ ³.Õv×\9œ³Ï8½$üˆåì3ÏàšáWÔ¼ÿ̵íß·/o¼2–çŸü¿r·=÷äÿñú+céß·ÏŸ®¶µ¥XDDŽ(#îx®¼¨)+_ÿ}8…9s§0ñ?ÿ`üÚæ\:l@Éø¿`±ã×w÷ù/LŸù ï>òOÆ­jÌ9ö'0N`ØÙ©¬{ëïÜ7~SæÌcæÔI¼ûÕbòªLvÓi—áfã·˜8e.3'É7‹³Ë…KÕÍßÚ<•÷?›ÂÜe+X¶p³×æ@\ñz¥©!-ú ¤­g3¿|ø?¾µŒUë7²~íJÎü…ÉË÷a›‰ÔOq’¹b ÖnfãÚe,ßVXÅ&’ÈÈÞ´ŠõY^•XDDDDDDê ¿×‹óÆP½ûö‘œ~Ú©˜†AÆ HOO/7mn]ÉÏÏgÇμôÊ«|ÿãµ~<—Û¿®…”XÛý8ðxFÞ=Š3λ€3뀑w ÞvÂÀƒžÿŸ±¶úõãÞ»î ::š¥¿—ï¶|ÅÊ•DGGsï]wT\k[[êZDDްŽñÿìÝw`ÕÇñïÌ–ôB jé ]Š€‚‚€å)‚ °+¨ÏŽ"¢OQ,¨Ø{‘¢"‚¢t¤# MZH/›Ý™÷G6 R!@Êïó^ÈÎNÍÙuöΜ½çNàåÀ—yuêhn= ÕštçÆïá†V¹‹'¸âùcÊÓ¼³×CXƒŽ\öôHnëäŸBç»_á¥È‰Lšñ"£ÞN†Ðš4=çvÎІ‹Žô½ó~–?ñ¯ÿw>Þzœ9¼ýZ¹`áÛwÆná§÷?æÅ]ñxœaÔmÑ“û¿šfêT(RlFhú_Bí¥ËY³v!“=XŽ@B«Ö¤Q[64íÑ›]sã·™[±Ü‘4ìV‹fµ Ú`8-º´gë¼µ,XÛˆ˜Ñþo?Dv¸Œ»:(æ""""""R6ù,‹€cH¤íÙ»‡Û†ßBƒúõ©Q£gtê@bBf®^¾Û·ç–‡qßCðÏÎÇ|œ‡Ozz¥ˆí‘Âý=7oÙ’ý\Öãð\½‚Û≌ˆàîÛoÅ4M>ùü Þÿ𣣖yáåW8pð —]:ˆ»ï¸µëÖŸP)b[RF¿ËF؈ˆH¹dÛ6¶e‘”p˜ßç~VqÿPk+o½žï»¿É—··D9U)+ºõBhx Ó<îÒQ"E}æûùÿoƒmcÛ`ÛVæ|ÛÆ²-l+sÚ²|Ø–…eYþß>ã2Öç ¨ˆˆˆˆ³³ &,²:¦éÀ0MLÓôÿv`FæcÃÃÈœ6L ƒÌi 0ÀÿÏ ¿ŽZ·xõbbÊeœã&4<‡£dwÂZ6oÎÿž~ Ó<º,Ýì¹?ðê“èЮwÝ~+£Ÿzšÿüs\ÇéózINJ"<2²ÂÇöH3¿ú€ó/¹´XÏ+¶…zÕ•\1d0«×¬åáÑOd_OÃ07æINoÝŠ?ù”>þ¤ÂÇvçöí´ì2 Dë¨0¥ˆˆˆˆˆˆHAR×òÞ¨k¹øú'ùz‡Oñ‘“¶í|“¸EY·a<ñ$ë6l %%…ÿÝ€eYlܼ…ömÛr×í·òÄØqÇü0M³Äc—רžl•-¶]:wà‹¯¿Éõ%è̲Ïë7lȳ/¾þ:Ï:•!¶%¥Ð""""""R~X{˜1v4Óþñ$-š7ç–‡a;u",4”»v1eú,[¾¢Äû°3¶ÒÅöd¨l±Ý½g cbhذ!+W­*tÙ† °wï¿•&¶%¿b)϶58ý‚»¸w@MLÛÇî YïHeõ”Ñ<:u>ë;¨Z=ˆÔ]«øæå'yqáal#œŽ]Zà6lâÖüÉß>À>ÌŸ«þÁ‡IãΈ:êž@>Íýg²ôŸx¬ÀPÒ’d„â*j€û3¯>÷‹þŽÇUŸúÕÝ$ÊÀ¢ä¯ˆˆˆˆäjãš&–eûú†Á]wÜ΀óúˆËåbóÖ¿‰®U+;9W%2§ÓIØF?ü§·nUâýØ>f9K¤olO–ÊÛ%Ë–pÉE¹ì ‹/`ñÒ¥•&¶%~-t‘òÏM³ÖM0ÀŠÛËÞûà/|øýN¼®æÜðÒ»L{{2ïÜÞ`âX4ûwÙU:u¥•ËÀ÷ï –î´°þdéÆ lg ÝΨuÔE³»€i_o%ºŒx…Ï>šÆçŸLcüÅõ0ŠÜX‡ö²×cc´áúg^൉oñé[·ÐNõ¹DDDD$ÓáÀçóÛº†Á· oŸÞÙÏÍø~oM~`Yï½?—&¾ŽÏçcÈ¥ƒJ¼/˲p£doE‰íÉTÙb;cæ÷¤¦¦Ò±}{®ºü²—zÕ•´oÛ–””fÌü¾Òͤt‰)"RÞh,:‘Sw ÖIX¤,É=~™Þ¿7²ÉccÛ˜|Û&çZÔ<ð/û}P½jÎjó+—ÿÃâe{¸0j kÒÁÙ¬'½ê˜pĽ ï–õlH·1"Îà¢~õ4GÁÈø³û«ß‘.µ¿açÎU¼:|8?Ù—K.½ž ‚õú‰ˆˆˆH6‡Ãu ‰4Ã0qóMô;÷Üìçæüð#SÞŸîo2Û„‡‡ðå×ßðù—_pèÐ!nºáºïÏëõb–³DÚ±Æöd«l±=Ç+¯¿Á#ïåê+.§e‹æ|þå×lݶ €Æ 2xÐÅ´oÛ˲xåõIÄÅÇW𨖔À""åžQáÇ+)»§`e€EÊŠþ\¾žtÌju¨d€ma†»!½þÓ‰:¹ºóa-‰rF$ÝÎ:7W,eëï?ó]ÕÕ¤à¢u¯îÔ69*œµM00üçº?gKn|võ¿ü‚¯~\ƺy²nÑb®yæ mâÒË(""""8].ÒSS!(¨Dë]{õUœ? öô¬9syíÍ·°s}YrûŽ4kÚ”jÕªe?·bÕ*ÆŒÛ[âãôfd\)b{²UÆØ.øõ7î¼uíÛ¶¥}Û¶G_ù¥¤ðêo²ð·ß*UlKüZè4*"R~†¶atë78s|ÛÎÓ ‘pî5 LÓÄ4YωÈ)bã‰ÛÁ²ï§ðêO±X†‹Æ}zÓÌ Žú ‰qüÂ:o<Q=¹l@‚ ȈßO¼;Šêþnü‘]Ï¥{ÄR~Úô :¼Ô‰þ½jäûõG½bë–3{Á¿´éS 7^’“}gÞ$âÍFœwÃCœwåv>xd$S7nç÷å{¹ºI}Ó$""""8N’}>lÿ½¿â:·÷ÙÙçüøãQÉ_€÷¦½ÏÓO>Aï³zŸÏÛ“§ðï¾}%k‰Û6>Ÿ§ËU)b›åüK.=ñW9•4¶™^»n=@—Ψ Àž½{ùcÉRf|?‹Ã‡WºØ–øµÐiTD¤œ3 ‚Ã"°- Û¶ü••9'^2sÀ&†iª ƒÈ)c±iú=üçCž _fÈpR½ó Ü?¸QæÅn­Þ\Ñû{Æü¸EoÜÍ©á„i$&pîèÉŒêä¿ànÏÀ³k1ïë½dx ªÝŸ3#óÿoÛŒîÍàžßñôüƒü2áVþx;œ +…´V·òÁ£EïÏ»åsî}p©Q5©œÁþÁÔª©z""""’såi¸œN<éé{½Ï¾úš+† fÞÏó™ÌAÓ¶M:´kÇŠU«J|Œžôt\nw¥‰íÉTÙcËÔé0uúŠí1RXD¤4ÁÄ0 l€­ü¯ˆÈ =ñfþ“ùËPï_‘“ÎM•QDì9@BZ‡‹àÈšÔmÜŠ®}Î碞 ËúÏÒˆ ëíOóTíøð§lþ7‘D3„ê O£†+—?éê¦ÕùiõýdÖZ1 üO{ ,fDÒãΧy<ú}>ùy5[$P•áRб?ŸJz¡¬ß½“-û„E5£w¿«¸¥W¸À""""’·å@jJJ‰iß~7“o¿›Yär›·laÜsãëøÒÓÓËmÝc‰íɤØ*¶ÇËèwÙ¥ DD*•~9‰ i%~å”|ÆÛþÿÛþaðWÉœ¶lË_ÄÆ²|Ø–…eYþß>ã2Öç ¨ˆˆˆˆ³³ &,²zæ8¦‰išþß ÃÈ|l˜Ù_˜5 3³p’ad~•Öÿ¥Ú“q]µnñ,êÅÄ”ëx'ÄÅ\æz,fx<¤¦¦¡Ø*¶Ší ¶sûvZvP¢uÔXD¤Q2BDDDDDDDD¤â &5%¥Ì%ÒÒRS Rl[ŶŒ2uú){\n7¦i’žšZfŽ)-55s¬×r>Žªb«ØVdJ‹ˆˆˆˆˆˆˆˆˆˆˆ”QÁ!!¤¥¥áózOù±ø|>ÒÓÒ Ul[Ŷ SXDDDDDDDDDDD¤Œ2‚BBHJJ¶¬Sv–m“œ˜HPHÓTl[Ŷ,Ç_§N‘²Ëívãv»IJLĶ퓾Û¶INHÀå?ÅV±UlË6%€EDDDDDDDDDDDʸ à`N'I X'1™fÛ6I 8œN‚‚ƒ[ÅV±-”)‚CBp÷$Œ­êózIŒÇét¢Ø*¶Šm9áÔéRDDDDDDDDDDD¤| Äaš$%&H@PÐ ÙOZZé©©ãPl[ŶQXDDDDDDDDDDD¤q¹Ý„9¤¦¤à‰'08—ËU*ÛÎðxHMIÁ4MÂ""0MS±UlÛrF `‘rÆáp–øJÖ¬uˆ IDATq»Ý†Q¢mÙ¶'=ôôt sÜV—Û­Ø*¶Šm9¥°ˆˆˆˆˆˆˆˆˆˆˆH9år»q¹ÝdxëR@¾×VXDDDä”0Œ£hFžv[®)#wR8'!œµ\Q‰`%EDDDDDDDDJ_‘ à“¿&~LúæY"Ÿä®]À~õ∈ˆˆœL†‘#ÌΓ¸µý fæu³“†);g:ÿD°’À"""""""""'R¡ `» d­}D^¢7ߤoö6üÏuO±ÀAƒEDDDä$ÊÓN+$'kd·õŒìeJçIç* Oo`%EDDDDDDDDJO àÇüµs¥wóIü™ôµí£VÌZ*û震ʋˆˆˆ”†mäžÈyˆ‘Ù¾Ë=;ŸdpîD0þÒÐF~€³ÛžJ‹ˆˆˆˆˆˆˆˆ¿|À…•}.(ùkcg'~íܽ~í\‰Ý<í#·žßè9ŽH¾Ú¹ ?Û9Ëd>Ÿ3°‘ÝØ¿¹«@؆¿Ç°Ó8§'°ÊA‹ˆˆˆˆˆˆˆˆ”6gñ+ ù{T¯ß\Ï™ ¶ó+ •8Ö !"""rJå;ö/™‰Ü¬D­5þ¯¿ý–'!œ™¼Í³H®ÞÀ†Q¼$°ˆˆˆˆˆˆˆˆˆŸ£ÀùŽû[Xò7¿Äo®Á¹¾öQ¥¡í£v•wR™a‘“Á82käi f.a¶áÏý9 áìßäIêf§‡ °íâ%Õ XDDDDDDDDäøÑ¸àäoNÉg2×¶±³À9‰ß¬¾Y‰âœMåô Î&X _‘Sá¨V˜‘•Äåè.½d%t³ >çÊ“³^VÏßì1ƒmõ9Ñò$€í°GôüÍüµm+o¯_ÛÆçóâIKÅ›áÁòy±³’Á™;âèÀù”TBXDDDä¤È¿×­‘çaN28³Ç°a˜'N—w`‡3g àìöéOúfn#oOàšê,""""""""rÌ é|DïßìômÞ²ÏÙÉ_ÛÊ\ʲHKI¢I£úŒ}ì~êD×Äápè&žˆˆˆH“ù¥?»÷îã‘1ϱeÛNƒC1L“Ìþ¿&6YIàœJÑFvËR½€EDDDDDDDDJ“³èEìÜÕŸ Nþú{þ¦$ÆñôãpÎYÝñù|ø|>222i‘ È4MjתÁ´7'0÷ç…<>ö‚Ã"ý=…­ü“À9Õ¤•ø‘DÓD¤¼;ÖJyÎÜȵ¹\½sOgùæÿçˆäojr"O?þ}zu#==]¯ŠˆˆˆHgY–eáóùè{vF{‰ °¬%0Èìœ9&°ÿQN˜œoFž¶bÖ£Ümʬ 3"""""""'BºM‰%=5UÁ‘r'--ØØX¢ê6)ñº–€Î=öoîÞÀ¶¿ ´ùÀ_ÚÆ“žÊèïU¹g‘J,##ƒ'ÅÕ7ßMÓ‰]:KV/àܽ~³Æ)=aUj§s`÷fÒ÷ï?¢ªˆHÙeA¡D7:°È%^ß ù—Îz˜óœ=þ/þÐvvho†‡Z5ªë©ä¢kVÇ›áÉ,ãŒá/þ Yö9s`rþɪü¬2Ð"""""rªX{˜7q"Ë›ÜŨµŠQ:SÊ‹°*5ü‰`‘ÊÃYð,;׿¹žµó–~Îê lù|Þ ³m‹íÛ·±oß¿'½<´Ûí¦fÍZÄÄ4Ä0ô±-"""r¢™¦‰åóå|y0;Û›ùåÁÜmÆ\éÞìG""§JûöíYµjU‰Öi×®+W®TðDD¤lÉøG:^Ä—}?cÅ gSaG?µÙöû¶FôäœVáÇwEáÛÆw/Mà·á×1r@-½‡DD¤\Ë7lç~dQ²ê@gÿضeYîdûöm$&&Ò¹sW‚Nò`ë©©)üõ×¶oßNÆôŠ‹ˆˆˆœ–eù{g5&ýíHà §uiä*m(,"§\I“¿¹×iÚ´)›6m:æ}Ûû¦3¨ÅuüØa"kçÞJƒ“ýýe{oõáþøû™5çIºGä:#§ÏàÚšCØõô~¼½n™éåÛ2ž-ïç£F¢ äÒñùåÁzS‹HÅcâÓ¡§sÃ'{Iõ8ÉjÐ’3ÎÄwà‚¦!™Ë™áÔiÚŒfu#qTäxd,åù!—²jä_ô9Þ°ˆˆHrt¸ÀòÏd—{Î|ÖÎî œ=³ûöýK»vHKK%%%ù¤þ¦iÒ´isV­Z¡°ˆˆˆÈIãoCvvig#WYhÊ7ß2Њžˆœâ3X1džË]Ñ`óæÍDZGëÞ}™9îš„-|™×ã¹n§äܰdCnhÄÂO¯§‘³l¿NŽºWóί=HÌþ>º—¿ß¿•›>«K¿Nz#‹Hå%îÀ!¼Ýcö¸¾¦Ç³wó¾ï9uxaÓçðÚÅup8ZqÛç¿r›&""R)™G^ì<…¿ð3Ùcÿf=¶³ÇΟÇãÁçóÚKøD±, ŸÏwÒKO‹HÙf%ì`Ù‚Ù|öýŸÄÚeà€ììûyÓßÎü=–^ )÷Žj#ñEÂÂÚœ…}±PD¤BJžÇÄ7·pö˜/Ýkï½2#oÕNdÕÛ7Ó³qU]„×nÍÕS·cxæq[@º¿°•¬V¤µí%zÕåöŸ=…¯›€ö½iòë]\1v1…~};c3Ÿ¸ŒnM¢ ®J£žÃxcY<66û¿º–úA­¹ÿ×$ÿ²ëyñ¬Hê\ñ {­R<æÀhZÑ•®]3Ψ¹–©ýË9ϾÉMz_‰H…fVkF÷=èuÎù\>âI¦þúïô?Ì»7ßÉG{mð­aLû š?´oQŸ%¾ùù¹kèÖ° A¡Dv6O-J÷ÏÛÃ㮤Kƒ#‰érÏÎÛ‹/û&Ë.¾¸»/šDäÂT…˜nCyù¸Ì–}Qó ý\)Æñ‘Áï÷†Ã00Œ ’R¼m¦mâÓûÒªfÁQ4ï7šy‡u-"""CßçµxtDùgrnêå)]ŸÏwÊþÈS¹o)›<Ëßã¾'`v=ú·¥ê©îmæÝÀçã_bvj4Wv¹‚³kkÌr)çŽ.ìÌžr¶}tè<-Ouÿ‘JwÂäÀ7“øŒËøðª®t©5ˆ±W¿ÁÇÿ â6hßÚ ÜpçLꌞÆüþµ±öm$¾^t±J1—t]gÓá¼ÿT#Î|wvXÄ»ÕÌç̜̯ä²ðÀ„¯˜X7–ÆÝÁ=—ÜGÌ_o3àâñ¼4¸#×Ü:–A‚³Ú~ÎuµŽl£Í‹oí¦ßk¿ðøàê@û×v0»É>ûu"Öà’^bPÇk>ü U¾ßÈyo|Àhó3XOåùO3¸xÊ´S\D*)ÇihécΆ-ø¨^üskÜ Æ¿ú­YÅäQÍóÜ0¶Ëø×7pú#«xëŽæ8€Þ=›¸¶ã_øŽ‘&Â߸k~ýÏ錓ޜ]g;?tŸÎ÷+2èߣðùçµ)âs¥{ÁLJ¿ècP­¦´nÝ8û @E}Võ?ã&¾¿›3ÆÌåÍ;e®×« NùžßôV‘ À<²tóQ‰Ü¬ñ!'Ñk±¬M‘=€s—<?"RùØúígüždûüŒ9ûõß³ˆH¹{È\mÆìÄpV󱈶¨Úq"RÑy׾ϔå-¹îÚ™_ è ׵bãô©üá¯léê>œΉãå~mèsó³|¶ò@f9Ïb8¶uƒéôÐTžnµ€‘7¾ÁÆŒ#ŽyÓ*Ö$'òÝu $00à&£øÕ“ÈÞ½ ™ßë©1ˆñÿÀÞ&³¢Ë“¼0$ºØ½rK~Ì^VMy‡%µ®â¶‹ªëëD"RÉÛá%?·flXÆÊäzô<«ñQ½…¼—ógJæ¼ìâúÎ&œÕ³É«–±©€´#æ4bÌCŒµŠœ_Ô犧ã+𓡈mfl^ÍzO=ºuWÕ©˜œÅm1ØyJ@Û9I_õ‘%u9Ÿ}·Ÿ³ ]Úd銕|>c ÜØ$çäåû›¯þ÷:3Öïbßáx’Ò Bkµ ×å#¸ãü&„øïþØIùê7øháö{¨eáÅ?ºÇ̯bÜéT»èy¾Ù>¶LÎ S¶áì<’ÏŸ;ŸjÇs'É:ÄŠÏÞåo±~¿‡àèVœ=xÃ/lAxAÛõýÍ7Ï¿Á7mgÏx’}Ôlv&ç÷¬ÊæŸbéÖÃCç‹Fpÿ5í©’µß!–}òoÏø}„ÕiEï+†3â¼Æ>öý1• ïÎfùö8¼Ôlp6wŒ½‘º]&"¥#+Ñkd—‚&³—¯¿´§ø³zÿŠHe•ÎSÞgmÊVláäÁܳÌ=¼÷Óz …€6Ü9s#~œÆk/N`Xçñ¼òÔlæ>Ô‰ ÃÄáoFFþ×å…­[Ø¡¹šsÛ;Ï3÷Œ‘ÜòêCÔÎs’·ÁŒæªÉ³y¨cîÛ &¡ÑU3ÏèöaV-XEJX8,ùœÛn`DÖ¸¼¥}ÌÞ•|úÙ\ú.]U·SD*1ߦ¥¬ˆwpZóÓpWìs«Ó¶ ¹·{l÷} § '>«ó‹ú\Yg~ ù]JµÍõ™ÃÒØ–îj‹ˆHÅdð¹^ä»ktà<Ý‚ Z^=€E¤Dlb|Ëχ °ýEÜ]j™>¶ÏžÁò´\‹YûXóÛJ6ì:@2!„ÙÄïZÉ7/>ÆëËSý›:À¬gäÅ™«Ù“ä 4ÔËÞÝqd_ƒœÑ­%.Ãæðê•lõö!V­Ú‰e8iѵSNrõ˜þ”V¼q÷NšÍª=)˜&‰;—óÕ„û5}3­gíãÏ…+ذ+–4W8áî4ö®ÃÛo|Ä/›q9H=¸™ùï=ÅK ýgáV¾u?£Þþµ‡T &åŸå|þÜyæçØÌ„Ì¡¹_/Æ}#ó~ÁÓˆæÒg§pg‹ƒ|qÿ0^\q€_ÝÀ=ÚãØ5¯–$`9ê2è¯1ªSÉsá‚g~÷_TëҖΕ¬Úµ”Å»n YÕÕ,ÛègsÎ<#ªr@^Ö½u=Þ:ê $çO9ðS¾ý‡ £ç>ò ŸÉÞ¯çæW—°þ³OømУœí.$F4—>3…;êÿÁ˜ëF3'ÎE—‘ðÂßü÷zž_Ïò%ñöê„ãÀOLùf^WKF¼õ"Ccœìûîa®ya) f,à@ï‹©vp7{ÒmŒ€öÜ2a—Ôp`y½àÔÛNDJ‘íïLæ—ñ ü…dŒ¬1B×U‡`)o¢¢¢J¼NüÜé|}¸ ߨ›ö r·8-Â/ëÄø'¦ñÍþ!\›4“7ç[´>½!žÌ_wªV§ª 8šqÁZñÔÿdD»t†¶­ ÛÖpÐÜ·õ»‚×-’IÝ«_æÙÏ;pã·9©Z£úÅÜsã3 xþr®v> gÖ' y'Å6æú¡ÝKø‘Çîþ˜ª£ro‡Óq¼ñ_žñwMÈw7Ç`–ê1ÛÄ.ù ÎNÜ×Þ­7¢ˆTžÛ&Ö±`þ|=‰ìÛ¼˜™SÞâ“õ5¸qú«\m’“­-úÜjT¿˜{oz†óžº”kíGÚµ6®¸í$7ÂÚ^Ä}·7§Ï¸+:†kZÙ¬™öÏüÕ’{&^àÿ÷øõ¹^ØñÞ”§òÊGÏ2±Ë­´±wp ê".ïVÄ6«ü‡ûïhIŸg/å FsK¯ú$-bS’nr‹ˆHÅPè%_îÐy¼6¹>jé|·¥À"R¾¿àûõŒˆ®œ×%ÃqçöiˆÓNfñ¬_(p(`G­ZÔÀÄ&>6àûço¶{mÌ*í9»màtçí`ÖìÁ9­Ý¾¿ùõ=¤¬^ÊêtÇi=èY§°S¥aš˜¹ŽHjø¶¬gƒÇÆŒè̽jàÄM½~çÐÖi`%mâ¯|Å» kJ«°-RSÓ°Íj4oZ›¤„„Ì¿uË:6¤ÛØžu¼q}ºŸ}.—Œ_B²mãÛ·—}>p4èB÷º.H[Æ ×^͈qÓ˜¿3]¹)eG´³†É3Û.F+RDää3 £X?¹½öÚk%´¯é £Ðu‹s× C_zŠsó NïççòõCØõÞ= îw.^ÿ8.ÚI‚Áê—þËnàù‘í œÍogüµ™÷ä|wØ.åcö²uýf|µO£qˆÞ»"R8‰¨^ Ç㸠OÎûÏPF½:¤.ÿå«‹™tqœ±zs)üÜÆYÿ›Ã7ÎßoÞÆ ¾ýtÛ‹|ýW,Át3“ohÆêÿ]CÿþCÿWKúv&£Ï*¥¿©°Ï•"ŽÏ¨Æàg_cX­ÅŒÜ oüŸ®ú«ÈmÑåÉÙÌzº'±ÓïePß>ô¿òEÖ×íEÏXDD*@‹!çê³ «Ò|'ó”óS‚UDJUkgÿÀ6Xq?ðàù?ä™ë[3›9ÿ\ȵ òoŽ;]þ䮕3FŒàõ\nÙˆâ¬sÛòúª¥lZ0Ÿ91+I°4ïÙƒº…¶ú´¸é]Þºªžÿâ •¹^¿æÚS©eV]¸\™cÔøüƒè8]þÓ¸å/®jYX€Иs.íFÝ\W}Fxkj8gn{ù%~òŸÌúƒ5s§°fÁ"†½ü*76SÑ<)E¶mø{ÿfêÝ[Pw_u‘“¯]»v¬ZµªÄë 2¤d;3ªsõ—q\]Àl³Ñ=,L»Ç?õ0?nx¸àm6ãÊ—àÊ—ó›YĺÙÇS“[æ¤qK~-Þ†#˜sxÄMÓzôôCú?zôòuG¯ etîg8cÜÒÆ•ò1ûÛÈ]ŸßLúózÿŠH%aTãòvsùE,çhÃã+Sy<ë^I÷"έ®zœ÷ÈGœ÷H~ÛªCßG>¦ï#ÅÛ×ðMê5þ‰¢æþ¹RÔñ¹›\ͤ߮fR>ëºMGMzÝ;™ù÷êm%""Ïq~™ÉÎçQKGïÝó/Ìù— V`‘Ê"m³æÿ‹e8 «Yúõ²~êP-ÐÀÎØÂœ·à+ææ ›ÐØi`%üÁ7?ì) lP­×Î ïú™ôó!lWKúõ®{Üßút4nIs·¿”ïìÇK;çþÄ*¯rÍë9‡ ‘}R¹7œ#¦1œvFé5ÏæêaøåÆaÜpé@.>¿35 À›Èa³ çô'qS 'vÚß,\²;Ïx9""¥ÓJ,NKQD¤lX¹re‰¯5W®\©À‰ˆˆˆˆˆH™RpÑ'ûèÇy«@—ìF^i$a•È©’—ýÈ‚X#è îyó)d—›ó²îÍþÑNvÌû‰u×6¥M1¶gF÷ãÚó¾â¿3÷²àùë9ÿªxbÉ€!‡ÍÊU²hþ¼·s;ïê ¬¿"""""""""¥"ŸŽmù'Yí£Æl#ß$±ˆÈq±ùý‡?ˆ· ‚ÚŸE·ˆÜÙ'Íz÷¢¾¬½¿0gMzñ¶i„Óíž <7ì\ÚÖÁ›pxoÕë5£SÇFäì"€¶_H —f½.8‹j¥‘Œ0BètÛxž¿±­j’‘ê#¸öéœûs¼x}3̺ už^›0G0‘Uޱ³I‘s?ÚÕÀ™ž@‚ÇMT£fÔry°ŸN½úáxngÓÖØQ-è{ãhFö‰PîE¤‚kÔ°!«×¬åΑ£ˆ‹‹#>>ž»FÞÇÊUÒ0&¦”ÏçÚv´‹;üˆˆˆˆˆˆˆˆˆˆ”ˆÑwÈðœþ½vÖ˜¾þ’ÍØ`[Ø–e[™ÏY¶eaY>ÿo Û¶H8|e g结 ~¦eËÖÇ| ¾€o?ÿ䘷±nÝZzõê­W\DòeÙ6¦q‹žçÆGç°¿ÎÞ˜<‚ÖWD*¸øxFÜq'ÿìÜEãF 1 ƒ-[ÿ¦vt4o½>‘ªUª”Ú¾:õìOx•ꆉiš¦‰i:0ü ÃÀ4L ÓÃÄÀÀ0 0ÀÈü‡¬.Á†¡¯§ˆäTCÊ}ÝfcÛ`û¯Õ°ý×mVætžk6ÿ5\bÜAæÏú¼\ÇbÅŠe pÞ"""R.Íšõ:t*×ÃÙY=û/Ï5Ÿad>6L02¯ó ÃÄ0ÈœÖõžˆÈIQ@ è\%úŠÑ £´K3g%|‹zþxÂ""9R˜ÿÔu¼¸Æ"-6Ž"ésýZ*ù+"L•ÈH&M|•wÜÉÖ¿·]«“&¾BµªUK½MgÛ6E]ËÛ6¹–Qh‘ãå,tn‰nÚElªôËùiL`)V,q©ŸO9EJ5œššªˆŠˆˆˆˆˆˆˆˆˆˆˆˆˆœ"¥šNLŒWDEDDDDDDDDDDDDDN‘RMÿý ETDDDDDDDDDDDDDä)ÕðûBET¤w4ÝÄ€(""R)¼1å+ADDDDDDDDä0‘ŠA `‘ B `‘ B `‘ B `‘ B `‘ B `‘ ÂY*ØmÒ¹a0mj» õdß¶ÕØé‡iÛº9¡Õ’dFñ÷Á4þÜ™ÊÒm)¤fXz%EÊ(ˆät‹t¯×gcÙ¶#"RdŽÓ}6‡½¸íêìÖwEDDDDDDDDÊ«2•®SÅÅàŽUéÕØÁæõkxᙘ5g.‰I)àpP¿FU:wkÁÅçæ‚ACè׺^¯ƒùølY,{â2ôŠŠ”^Ë&6ÙKbª…¾""e‘adþtÔ©â"-Ãâõyû1MÚ­Qa.IDDDDDDDD¤œ)Ý;œÃzFñÆ5õéÝÄÉÄ /0ð¼ó™?û;ªš´¨FÝ ÑN6®Ùʘ±cûß»Iüw'.'œÛ*‚ICcÖ£:n‡Q&mºiwZ8¶ "²,¢á i“Hw«Ê™úAJWl²—yHHõ)ù+"RŽºL.îIÓšÜóñ?L[tPgq©´Î<óL Ã(ô§G ”ˆˆˆˆˆˆ”9§¼píH7_MÃjø<©¼óÊ ü8}2£zÆÐ Z0Õ‚¸L £j•ªØuØüÅëxzÌ#Œ}ñUÜU±MƒK;V¥U žþn/±ÉÞ2hwýꌊyàkÖ¥wªÈ äÂsjÐÇÁWÛó[¼nïÊñ³lØ—ArºOÁ)ÇšGrm÷êLúy?›÷§óÐÀh•…‘JgÑ¢EE.óÛo¿)P"""…µ‡y'²¼É]ŒP ]‰ˆHyvJ?Ç×`üeõhX-°XÿçïL{÷=nîÕfaРЦ^ AÃ`7Ñ›”ÄîßÂéU ý_‚C£ø|úû`¥e÷Ni^+ WÔ'¦zÀ1“Îc#š2óî¦L9'„ ²òJ\yUfÞÝ„ ܹ^8“³/hÂÌ»›ò~Ÿà²9¨s™Hhí& è\‡ë{Tãº3¸°Î>ÅïxÚÈ6ì>ìQòWD¤‚hÀµÝ«³bG2÷}º“´ }YLD*'Û¶óý‘²ðAȶE3ùñ¯„ã¯^äÛÆw/MàóÕñª„$""åÞ)K׎tñÔÅu‰r`ù2˜:y2m¢Ã³S‰ $,$ˆ@— §#—íÆe¸-/¾=ÛpâåÞ‘·ñÍ7ßsðÀ> ,ÿ– ª‡:yò?u¨\ÒtžAL›*tò玫7«ÂÙaz“TDžðúœ³™Œ›øfY<_o aSR J]»} ¤{-BD¤iS7ˆN1!ì8”ÎÓ3÷è&ˆˆHi³÷ñÖyADt}œEGVeJŸÁµ‘ôyme·•íåŸO¯å´€º¿°] ˆˆ”Ó£¤õ|ñÄ5œÕ"šðuisεŒ½»ìŸÛ3–òüKy|ö]¯ˆˆˆärJ:<8 ½ ¶?ùkca˪ÕibäràÆ‰Ó¶qáÅa6ø¬ ðxƒÐˆ`zŸÓ›ôt ƒ¼ëVuòØ…µyàóx}ÅüøwsQ›¾tVìqÒ¾^¶à‡ßÓÉ.(m0°oçÕtS=ØA¨Ó&)!?VàÝµé¤ø3Ø3ŠKåô±;ÑÀ þF“ƒs/jÈ= M¯ÙŰy)d`аk}^î€oÇ>†}Ïáã ²á¤Mûê\Ó&„¦a) i,ZyikÒH,h3€þçFÑ¿–›Z¡‚M›ƒû’øa«—†MÃi_Ý©V®9ÀëKSˆË «é¤]‡ê\Ó&”Æ!ŸÆ¯Ë0u]:©™CTÃj ïNÛªœ^bywƧžš7¾U<{Xz ‡m/¤ÅOæ`ÈÞ€¦ô브¸%nVxýÓKˆ]ÄJ«)-Z{iâ#ØáÁ—rM[³8¡Z¥ndNöªç¯ˆHuiÇ*üµ'•;’™¶è ×u¯® ˆˆ”*›„%ãrC#~z=œåç¸̹—óïú™ô C/£ˆHyýŠû…Gú^Äs›ëñŸ[âõn1„¦ý˺ßç›ìBgx‘òé”ô¾¦[uTË*ќٌHIŒcËö„¹q6N8 .L‡ Óa`˜f`N‡ OR<¦mЫgw~[¸Û²³/B³4«È«û¸"O‹àÌHÛÇÄÅ ì³ êµŒätW®… '-sZ'ÁøHÈ0ˆ¨Lÿ>u¸¡¾™½LŸ~u¸µUµl’Ò jE:rÛÇÊíidu‚i`†ƒÖuܘØlÚžrœc›´éY—§z†Ó:ÒÄç…Ð*Á ìS—'ÏÀUÐj†“V‚iRÅI€å#ÑgP£v8×ô¬J÷(o†M`hgvæ–Æÿ+gÒæÌºk× o²â#eB°Û¤k£¾Yy˜CI^ED¤”´ïM“_ï⊱‹)´±ƒ™O\F·&Q„W¥QÏa¼±,›ý_]Ký ÖÜÿk’Ùõ¼xV$u®ø„½à™ÇmuóôÔµ¶½DÏ ºÜþ³ìDV½}3=W%Ð@xíÖ\=u{=¿¼›ßâºçÓ{òÛ\S[£$Šˆ”O)üúäÍ<¿±5Oüô_ÿFk'sÇå±7Û{ÀÚÍÜÑ—2rò:•ç/zœ†a@º×fÊo‘Ò¾>i:œ÷§\IÜsWqç·û ¸Læ×‡rÙ»Iôæ+~™7…[ªüÈ=—ÜÇìƒç¥Áñ¼vëX~OÎ`ãë·óÔ–þ¼8aÑŸò÷­À wÎ$â¦iÌ_ò;³'?Â5¢ó¿iàYà 7l+_6zðbÒ±eÕ º¢µ¼lü׋„;pŽ*Ôs€šÊ¢Ý>l8ª µ˜Ä½6¶À1n‚êÓÊ ¾I,.¢û¯mƒ•ûçÈ›Q4q‚šÌ[¼x±Ù½!‘u4«R¼Ës;=ÿgï¾ã£(ÞŽöjz'!…JH½7¥(]ù Š ""6° úU@Q¤ˆQ¤ƒ…ŽéHï BèÞïn÷÷G"F/>ïy‘½ÝÌÎ<3»{w³3›”Ÿo;“4±çòËê`—_VC;ª@ÑÛѯo5V åÛvŽ8zg#°]ÈdkŠFGž{"„ñÜœz»òúÔa4Iø™EësAñ¦ÇÇŸÒ3e2ƒ æ™Ñ1t˜ø ½|Ë÷±_M*S” !ÄMøÄLý7¿ãýˆµ¼2àsbмÿ±ÚÅÞÌt–ÀÞÎ;;;ª½Êú¼tΜɿOñîÉ„;qfÞ7ìh4š{ù–ûC¿±é ^o—¤öµh;p‹v&PÜm®ê©9¼9!…þ^¤¦IjM!îjZþ·:ŠRòÕ³ÝY•hѪʥÏr†j´jáOæ®m*aL„>¸*Áº $&©e®/ë—½™ùy0”³he¥i9¼‡ƒy•hÒ4éòBq/2ÜÊ5qÄl(æÛn5‡c[~¡A n¨˜ÜUžö󯍸ˆ«½ÉïÜ›–ÒéœvŒ\Ϫ„…×@gÐÈùsgð®èWì~íŒ:;²öP #¨ ´ 5¢C##ÝBÊÅ7. ŽNFÜfÚT·ã‡M9嚆Òz!—c6ˆ°s¢cX»÷ßs‘|8­MiQу'=u`ËfÍ¡¼ëžVÔš˜Ãa« u칿ª=16|œ‰Ð–—Ëá‚gYØ z©+8ëPÎØ®©óÆš”K¼ ÂõzLéé,Þ›K–F{#.6 4@§ÇUËå·õ§ùm‹™>=é[ÑLã #óÏ]y¯…ÍÙ(»cœÊ0‘¾ÁÛšÆa«;:5•3Ù5hä·Ž˜i&ï5Š—™{5³Úˆ;…uFo7OeöŽ'^ß,A¼ÛèkðØŒ?yLòvÕTUeøK9`9ªÙœÀÑ^‡—‡wW=z=¤¥«$\°–¡¢tú*Ù1Øûweب¨.mGÜ•+˜ùã`þï[ŽfÐ6ÌY‚"„7š1Œç¾ϯ _á™ÉoR讦ΗG¿YÍ›õ.ÿ(¯ÃÉ×#ÿ¾2-™]kw‘åìÿ,fY\ž­Rðu½¢C¯«¥„G™kñâŠ:ý>‹©?á©øì½ÕüúfýËF…i$®šÏ/ ѬníΧ¯Ù,Ô7#¨°{§guCÞå !Ä]ò1Í¿•mlص\Ú`WìVÚ5}g¨ŒP±©åX_Ö5î€ZzŠ»¹º¬4*ùeSe˜€Bˆ{Ó-½Á©N%‡â¯Ñš“¢£mŽäRÕÙ‘øëm„ù¸àbÖ°·s@oÐÓî‘G sV¨lIÀ»¢'ŠNáøñã¿$/éªY¾‚œiâZ^&_Î;Æ Y—~ÞÝ“‡ …€PgBË9dIKMcÑA VEO“û‚™3°2_·uÄXt»Ü VÆXÐŽ&È9‘ƺ´ë³–žÆ¢}yX-;†0ÿÙ*Lk툓¢qtW2[rÕ‰ MÑÓ¬}/„\[SÐRÒXmÁªhÜ&ˆ¹ƒª0çÙjü00ÇýòëÁàíÁGOWaN¿ &=ìK× ùo°Î¥ÚnÛHL£Á wÿÚ×õá‘(=mœ²ï ‘Ã6#hç9«ïÚÎ ½y¼ÖYÜ2m¤©r?`Q¹Ö«¨Á¬5|ÿÝQ ŸÏ‹MÎðÃW+I-õÏ-ì|'‚ê>öTóqç…%Ó¼[޳fücôj@í _ÚtÄÜ]SÕjéœý}úáïJÚõxeAüe7”æ¿ÔSüòöƒtkDÝ@gÂýhßûMæÌz‡¡Ö¤a°;‘µñâ”…ó~­y*5¿6âfõ¦CMo"ü]ˆ¬Õ€Aã~áÜå}îêy¶NHŸF~Ô p¡võP:w{ŒY{¬eçë¢Ö¿BÖïrðß›_ò~ã­šî<>ótA^4’çõ¤vÕGY‘¼)íÜiÿþöËnˆ))®*gxŠöážÔô¥õOñÝöë˜VøßúiLT 3aEÓ´“7Û96OéO¯ú¾Ô¬äEÃF홺5·`Ý6Nz‚‡¢|ˆ¨T‘ÖŸä‹õg ßè“{˜•ït§S„'5‚hÿﱩhÃ-Wœó%œ‰çè4{н1›wdòdžt,KâÛEX¸<™ï¼€f×g¯¦lÞ‘Éæ™l:haÿîEoG)£”³ò–±¼û+MÑú)OÞ.¿Þ¤mäãöÞ4íû5q9ÿÞŽ—¾4‰KÌ•€!ÄM¢îÇç·%fìXV\6á‚¡Zmj˜Ï³û°Êaa„]ü %ÀUh\Xù:ÏÍófÄŸky¿öÞü±ÿ^§u¨XA#>:¶äÇ1(NT½ÿ9>Yµ“ÕC½ùgÊ 6æÚÏ^_±ûÀ>vïÚÅ®]»Øµ}O…˜‰|yƵ“›g…ânâЖ>ݽ9=w_ÇÿßP½‘ö'X÷÷ÑKŸ—­‡Y»þ‘õ½ËʺƪEa>Áº¿\9;…bƒ=¤&§dRfšÕëiœ?;Hž´!„÷ [:8س„û€u&*7}€voÂÍjÅÞdÀ æ–GŽfÀ 0b%'+ ‹ÅŠÎjÁh4€¢š–©S§‰¨Ó¸ô}{•t²žúÕqQ çd[ =jPãÈ¡ NÕõ ÐÕ™6~‰<]ž’ÚØú× ÞM÷â‘pGªº0«6’’-;‘KÚeéïß“ÊáZ^T×ÙØ¼/äi•]ëN2:«Õt¤Š“BvJ6›÷$òí®œü75Z+ÿ8kšzk¤e]k—Œ-ž`tŠÔp¤ª«gÕÆ…Äl :ÓÉ*áîfª8kd¤çð÷þD¾>tû:€ÕäC¬I.ÜÀýb½(鱬ßë/®÷ÌoªbóFÿŽ ÖY±á²åÿš¢Ï¶.™FÒê¯XÅC|Ò³!‘Þݘ:øK–ŸêÆc%u¬ n=€Îþö@&ÛÆv祟øîBFù%±áÓ—ÓÿMü×N£ùÉÉ s>¯Í`N[?Ô„C¤ûW¼ìN—âÒ¼<›É޴Ĉ1Lú¤Æ”mÌ9‚wÇÔáÿ†a⛎$þ6–÷>xš)õwñVcÓuåÉ]Z~ux5À°iCñqÑ8·þÆŒÀ¸šû™ØÙ…löLèÆ€iY´yeŸ×÷F;±˜^]À®S6ž¨[j¾Z]¸gGíæ1/ÚÆždð ¶Ã›ÙœÇÙí;Èíï‡=¹ìÛº­Î[Ôw‚¸rÕU>ûª=2°ÞÊ ÖLù<ý&!ë¦ÑÒéÜ¿õö¯‹9ëNq)Í+X”Íž»óÌV:¼6—#ÝÉ;ç #Íî»3h†BçÿÍ`X˜Æ¡…ï1±or~þ‹!uì@KeýÈî¼ö£ =†ÏdDugÿ™ÅôÚeG|f9ã :ŽÞmù"%•5kÖ™Yø„÷^õ£s;WÂÛíÇÍÍE‹ZߤŠFPpHÑ héí¤¬˜9–§ŒW±¿›TŸZ^ ž{Œ9ºçùjÚBŒòîvp²»t6MδJ@„â¦ÑðØ$Æ-ŽbÀÒK]µŠWw†ø€Nãá1Ã[ôoˆ9óû“ªÐïñ&8§ýΈ!óñxu/GÕFÿù~l8‚—¾y€åƒÑé«Ó¹[ï}øÏÖÉåñHˆÛKbÁ·å¶#ËùbJÍÚ•pÌ;ÁšÉàá…G‘·ëz×JTw½üCAž&;ïÊ„ú9!Bˆ»ˆâʃc>¥÷ß}y¹ekv¾4êàdK"n׎V~™quåµçÃh;¶7ƒœÞ¥o„ÆÞY#ø` †NéŒëÈF×8¯î¼üôtxï!žÐÞæñÆ~SŽ‘Ò‹nµC‰ªmÇgóÆ1¥Ñ`jiñ$TèÊ#MÊHÓ½Ã^¨AÛqÑ›Q<Ó2sÆFeȈ`!„÷†[ÚìëVò7¶ž¡‘8;»’›—Eѡ茪T§²õ4#ç-ħBjçZIs²Çrä4¾A•ð®è‡Ï¿“ci7ØÏµ¤}ÛX»òkKZ{>‘Á“/{%“§âãË^‰ÛO×-EþPµ²ýŸ³lÿ§„76JþL$Îî&Üt`KIcù±R¦3Ñr™7÷0ó®X¡²fùaÖpåþwl=ÃŽ­%×Gvb*Ÿ-Nå³ËfcõO‡Y]޲îØv–ÛŠßOÞé$FÍI’£í¤–÷™¤ê1–~÷ž¯¦ú\èâ IDAT±£‚¡Íºútbþ¼}ôV»ÄçÚyW#4¬ò¥gk§,cæ÷§i>î7^èâ‰Ôøð8keÕ–OhfŸ@²æN³¦­¨S˨SfšÅ8ViB³¦õÐÓ߸ŸX3%œûŸèNs#жþЋí[â°5®Žî:ò¤%•–_çðû¹¯`¶ßZµÜ8´´)?nÁÖ¹!†´Õ|3ãÁϯcÂKµ1ZâQ¾U”+V­î»tSŒsã¶ÔfÿlËä‘Nœß¼“N®([×qÀÒ™zÊ~¶ü“Fµ^­ðÒYÊUWù·ëpìƒ÷×C4ð>ÊÚæò÷ -›^kž‚cµ´lQ=­hT1ž ÎËO³Q‘MSW2cÆAB‡naÜàê…Ú™–²‚¯¿¡úÐ-Œ¿®q£ªdF7aÆç«xê‹8%/çûE§‰¾œ÷žÉ/[cw¢çÿÂör¶ÉËã PÉî,ûöí+öšÐ®¹3U+› ò7±`Á‚+¶ pÉK:˜\ËßNʈY‹°²ËX4þeïï×§õ«_}›qÇdâ#¨ç"_+ß.ަKgÎl‹*Bˆ›IW‰Ç?}yk‡réÝ— mÆÿÊÏ^Ã=s(N×JÔ~x=ûÖ'îÓá|KV¼R7 æ°ç™ðÂLZŽ~‡åϤ«»È×ç1ó ¼óþãÌO¶açîKp£¶ÔõÑc9·‡Çʰ#È5ºR¯ ¿y:©!„¸§/9þ½øv“MÇ~ÈŒ¯‡1ÿT6³;þáxàÅl8ÐøÝ,qx•·>ìKÇóàùo.Èë íoP.J»Æ5ÁEq¦Õ‡¿°ÄóuF}ñ=Gda¬ÆCÚÑ%2€‡ÇMem¿ÿ1ê៰¸T¥ã» éÕ¤FiÚÓhôjVy¾ÉÈÏ_¦ç{ÉØì<¨Ö’á®ò\`!„w½[úQÎÞXÜ¥SCS½ÔÀ˜~Kz"ªMG†ÅB¶¢¡ä¦áêäAˆ>ƒJuÚauRILÊ%ýP4F'{<½|Ðéÿ-Jñ_ Û›î¤Ë¶Žfƒì§`vÐcõ[’‰±Iƒ÷&[ô\~ØFÏ uòO:¦<ôH8³çÍfç¨_ÎÔ¶#»‰ÉJçäÐ*Ô|ùßWUlvçÓÑ?<€-—0îÿ곯Çíßöµ¼Ð_sÎõxûUDÉL$) pô>øT€­ii×'CƒÒò›Ë‰Õã?å'vÆž%ÛìŽ)Ê¡~þ([ì6dúÓ¦U8Ækˆ•†ùâÙRñ¾Öµ_gæÚíäu¬ÍÖõû‰üöŸ/bËuLkÙr¢ -ÛUAOôµŸù«à¯K")åÆué«PBšÖØíÈ à¾¦•¯h¶#;ˆÎ*²ÎP•†ü˜üÇvâ¬=¨qt/Gò¸¿A¥?ø]MœÖ0Ò Ò‘­»/Íç¨(ð“ÞÔ©‘ÿ¨‚I£+Ñç…8²s.•ÉÙQO¿®.äO ¹ÒÛIY1³•£Œ\çþ®¯>UÎÎy–áY•¸b"m½å#øí¤×]jÍšÜ/„øQ”›xó‘âÃ3¿äðLqçÝgù%ùÙÂ/+Ññí¹t|ûÊíFí kÔ寘i8v/9c/{É®:}&ýFŸIÅeæüý¿køV¡>cdICBˆ»ù½¾O3OZÊàI%màÏýoÍçþ·JZ_‹‘;³yùk®}Y’Ý·`¡¬õ¥_ãþ]ßá­yt(&¦j1}ÃcL/æoJMSïCË—¿aÍËÒ„BÜ{î€{y@1šhñÜhRnàø¬ñœ;—‹‹MOwîNŽ(™ÉÄïÛMV®Žgˆ gëÎtéÞ ”âGþÞ™ß p5‚Ñ^‡.'—õÛÏñYŒù.UÜ›rÙ9.‡³2¾¹ã/_¥;ÃkGRÿ>§âN WÒ4ÐU¤ë¤¥<[ûòÓ—w“OÌÙC«µs˜=}2otø„Y×1sHv\Û)Â`4¢hy£PL ªZ0bÿzòdªYâ:cô$† ú]ßñ|8& /%–ù/=Áïÿ†Âfņ½¡„B••¯BõHËûjðñüÕìOMcí¶*´xëÿpÜð!KׯÓÍð 1QÝPÒi¼|§>=*jѾZńɹ™YØà²mìŒ 0™Jœd]Ñ‹O@+eV´²Ï¹JAw«ZÊ–Wg@1zóÛwU™õc±ñ¹¸9ëhÑÈ™¶MœÐ ¦TïÒÆ•]ËÃYú{ 'ÎXðpÓÓ»‹;¾™9œ7:JÏVF;)3få)ãuîïª.WÔ§‚{ÞDšÇ¬áci¾`õ]e°B!„B!„B”æ–vg婸Ø7φ†Å`ƦêÈņ³«Š5—ÔŒ,tvrÒ³È29`ˆhΡ¿âáãN%_ÒœÄ`2S¸âÊÎàì¼;hªB-K²BÚŸø/Èú›Ÿ–œ!ò••ŒzÐóÒ‘©%°úõ‡˜³àÞh÷…ûtì±·ƒô”´Btúʵ¨jšÆÁ£6*=QìÈW'‚Z â­VÑñ<þÍ·ìx.Ц%¤y½®+OÆ’×Õ=¸CjSÞÖ—¦n ¨.»_ý¨#Xÿ%;·@ ¹bôf¹òuik‚èNèÇß³ìËslöjË€`?œÚEðѪoY ÛŽo§1Ô0R0µsÙuuUt¾T­æJÎÚ?ØžÑfÿÞ·uëÏ`¬A P¯¶njSÍ<­b«Wx h}•(Âì?gëÆ8lõBó×YcÙºå4öQ„þÝf›þŽÆR¿V±1¼º8ƒÅµ!™[xîaüÃ@ƒŽ/dñÙ÷™øèÐë 1EãÐ1ß½ëNxHA®5ÄÌZ ®é¼2ÚI™1*G¯wšz=Gœ‚9|ǶäƒxñùJÌ›9€`y°Bˆ[ Y³flذ¡Ômš7o.B!„Bqǹ¥ÀgS-%tëò§µÔ4œ*…¢spÇd»€·£Žô¨èù5ú~ž´iцJÕªbÍUIµÚ¨V½&\Ñõqåè Ó)©m!nƒô5óø-¥/<ÖŠ—«*NÝ¢øzü~OìÉC.;n Õˆ¨aÇw?çûzϪ'ɳ3ÖïB¿GÇ3`êã¼bÎC +aÊ:Ilrezöj„}üJæoT ­Q û¼l9” nž¸)€¾¤4ݯkîÅãÚód;Vò:SÕškÓX0q.>Ý#ðПâdÚ¥Ž4Å«v{Ÿç?îÏûŽoÒ©ªÂñßmƒÆåÈ—S‘Bë+÷àZï3qò)BžÿƒPƒ¥C7B?É×j5žy§NñŒ’êªîÕDÑž&O ¦ÆÏcyíQƒúÝG á,;L`fl0½ß늻rmuÓÿ±ñôŸØ‡×´7è^ßcjOÇ®µòcÔNuF±ÏÚ—î5Jšþ¹„¸Ö½º(šj ç«.Lüð+¾xu&)ª~µÚóÂ÷ï1°…Ë5vÒ;ÓpÄ2¦{üI³†ðü‡Y<«Óá6´ð§ÎðŸ˜î0œ‰Sžâ©ðŠèÀ³ßÄÀºöoOäëK™á1‚O¿ÆóS°™Ý©X¹9íC] ÚëÕÅY3{î?ý±‰ÌùÏü­l€iB®l6°7ƒ‚Û¹ü¿QU+i¦(,þ\QB}Yí¤Lå)ã5îOD×W^âÏ×g0nvš¿åx]Gš}Ýá|4ì/zŽ{ž÷ÿÅó5MrŽB!„B!„Bˆb(÷÷T0tG+ø§¦¡i i*Zþ/¨šŠ¦æ/«ª MU󟩪¨ªô”D*?ùM©;kUÝ™×;ú³FEC‡‚ІFnâYf¼ü$Y© ¨ª o;ìì¸F4Æ/ªƒü©PÑ—Å?®â‘'Ÿ¦< ·ò ë§»NçîÊ«<¨rö/¬ÉÂzÁ¼Qéˆ{Ó ¡‡èÔ©ó=U¦Øó9R±wÛáx¸Ý,ÿ´“áõdŽÜ;—†ÃŽá8Ÿ^ˆÁäTê–ª5—Ls0é-£™\%tâ¶2ïøÅßW -uÛð¨æ8»y¡ÓéQt:t:]ÁÿzEÉÿ]Ñ¢ä/+ºüÇR+ JÁÛ»ü÷xŠ"Ï~BÓ4nÔç¶5«ßձرcÛ=÷¾Z!„ÿ«V-'*ªþ]]†Ö–Ï{Bq‡»¥#€ÿ‰Ë$×¢b6þ{‚W¸ø¼^Ն͚CVf*Göï¢r€?ÆŸ&-#j¾žœˆOAwv =‚©Z·6šAÁšINÆLnè”KEÑÐPP žG©‘kÕØz,³Ä|)f#Õ*qJ¸¾‹ÍJG!ÊfåðòéìT«äëŽ>õ¿OÂáÀÇYS:ïl Yu?ÄêYÇýc1ÙRÐíQtùcÑ5MEµæb±id÷#'âuÐË„ÇB!„B!„B!Êç–vgç©ü}(ö5òG1©ŠŠjÉáÔñx–/]²e+ˆ?u†Ä³gù|àý<\»Ñç³ÉÄ‚¿£‘µÃq°7€ Éé©Ìžþ9ž¾4lÑìÝÐô+(¬‰I#Ç¢–™?ךþ,© Ö³‰<·0‰7gúµò …¯ Sv6m<Ï7‡òÈS 4jV‘§"ì©hPIIIgÚâól+!Sš44!Ä ¦¥qjû¾ùy?§2ÐýmÖŸ‰ߤ®YÂsÇSò‚þ¼J=0œ_‹áüzôY'A³¢š¼°y5Àâ×Í(£~…B!„B!„B\íÞá­Ôųo/ÇãϰiËfþøk)éi€‚¦i˜ Rs1ƒ¯“Š£ƒ=Án(ªùx¡h* 6`ÞÜÅXÅ¡˜ ñÜYôš /oo«VÅ?07 ,Ú–T®¼å%gò÷i+¶Ô\2 <Õµ"­ìÍ@ïïDçö¾¤$g±ƒ'/F9`ºÁ/§l8›-$YJHG:…7ƒâAëQ¿Ñz”„⮦3b­ØkÅv !„B!„B!„7Ä-ï>“jå¡W¾&ö÷¯òŸ[¥Ó h<TÀâàš…É‰,$eæà`¶aÉËBMÔ°«àCNNN.v|7k1gΜÂÑÁ‘£qñX¬yhª‚‹‡;õ~ƒ³–*åÊ[ö©¦ü‘‰°¯âK7…s»Ïóá¦lt“;9Ó8ÄÈO zÀ’ËŽ=)ü“dCôŤ#„B!„B!„B!„·Šávì4ð¾ç9³…Ôø½ Úò_Ô) åÏÛœ“gáPš/üÕ\ ªŠ½É³‰¤ƒÛÐÜüÉHÎáÇ%KØMZff';⎟aËö½(zP¥^;Ì9(ú«Ï£³³;À)ÒŸ9‘—^ÏuÐc;‘Ä×1fU÷äí¾îœ<œÀ‡¿¥r\Ú“B!„B!„B!„â6ÒÝ–LDõŸŒÙÍ ]þ ü;þWÓøfîOìI3’NŽÙ™$UGzžÆÁí8–˜ÂÌ™ßó×ߛٲe¼+›—ËÎÝ»ÑÐPU0;ûØy4ŠÞXv†lVÀd§ÃTðRF– t$‘?œäNòÆ'øpW.6[üO¿ïO1縊h­¢+6!Ä=LKåàŠ/X¸þ4ê5þ}ôê¯Y¼ñ̵ýýí)4—³xÉ?$ÞŒ)îµDö.ýŠ%Û’Ðnvü´sl™9š/~=qÔÁMŽí=wü©»;ª.…B!„B!„âöÑÝ®›]*Pà˜Ý|ò§‚Ο MQȵZ™-™æOeÅá\•›¾í Û^Ȧcåè¾Îø¨)ÄnYÇÁsÙ·¿nvl︶tõW´înv]–t¸•ç"!„B!„B!„(Ãíܹ³o(Í^^ÄŽ™/|lŠRð·  j‚ÃÇN ZÁ,Ñ :EMC§Sˆ¨Qƒ_ÿ ƒ^kp"ÿ“³Wù3’›Á÷kR©ÐÌ™Ð'2/XpÌKaú’³d·ð …ŸõQILÌ#8WZú+h¹VöíIäû£*Ø®LÇI‘F&Ä]G瀇 þ^NùwÉØ¢ùyìŽõ˜E­ Çÿth´älØo&jX=쥥HlÿKnÇy è¹H!„B!„B!ÊÁp»3`vö¢Ñs³8òûtâÖ~‡š—}YG°ŠR0:EAQ@W0ìFtF=Fƒ!=:BÚö#ä¾çЮvfSÑçx5ú\á—Ó˜þSÓ‹n~ø/>Wþt„w]ÿ7…މ"T’7ÿÉAs#^©í áØ 9 !„B!„B!î@†;!:ƒ‰j_"°iŽþñg¶/Á–›…¦Ó¡iš¢CStè5 MSQ °wr%&«^]½‡¿Ô¨w”ï÷gÇ;z¡ ‘öÛ›<û¥‘ÎM÷³$öFokæùoGÓ¬è€=-™?OgîŠÄ&d¡³÷¤b`8íäÁx ÄÚz“ù´_zlÄÌèÃÃ3L4¾ŒaÍóÏIÖ¼ÏK¿œ'1Ç€{å¦t8„œP¬;ø²ÿëë1‹÷{úåÏdpn#žOÐèù ¬™ÿ÷éÛ¿dÔ†ÃÄžÏÃÑ?Š6¿È#Mü(ñ æjû~œÆœ•›ˆK²bïÆ¯N W¸ÔDö,šÆÜU›9–n!éÐïyºEzæ ÔØüå,Þz”sÒÈÅŸšð`S#~ÿ“½q ä9R·ÛPž}¨VáY ´DþY¿ûïPË@åôªQŒ›½óéyè]©ÙáY=ÖÝeûÚǹÄTr±Çãòøä`ã÷“YðçnÎf™ñ®^S†VPö¶òíÉøIO¨Ëßçñyyý—ÚŒø²Kéñ‡²ãQÔµÆÇzŽí ¦±xÍNâU\«5§ûÀi_ÍEË"î—©ÌXô7± 9\¨ßoCÚUD)1¶e´ÑÊIeÇöfµ…ÒÊzE<Ë*{õw5û*Ós mÓX^ÿè0ÍÆ|Îö`gÙ[ƒYêñ½Þ÷’Î-íʨÿ2ŽƒÒb¤½ò\TÞz-­m!„B!„B!îi†;)3f— „÷x›Ð_!áàZ’ŽüCÚéƒä^8-'PÐÛ;cöðÃÕ¯UãÖ½IFK qg2Q5²&¦?›®â¦`;¹ÃéV.DÇ÷ f,=ÚŸ–Q «ÖÞømï8Eä;çuÆü˜C½>Cy#ÌíüŸ|ÿÙï>¯BHqeÑÒs/Þç‚‚Có¥sW@+éVγ}ñç|÷ÁçøMF]cyb¤‘gq!²×0öR9¹æ{æNÞ˜/yªVqç;w8c¶ÒøÑaô©æ‚%9ÇŠz —ó‡óÁRhþä[ô Ò8þÇ7Ìý¹ã¦Ò;ÔZ:'öí!5d¯ ÅqÕ_}É×3C¹ÿñA }ž”­ß1cÖX…ϢĥBh ëÙíH½žuÉ/½‚[xú{OG »0óû÷ù®ò\†6sBùw_AO3äùê˜rO±mñ|÷)?>v™ìþúu&­q¢õão3 ’ž¤ƒ«ø!†‚`3aQ‘˜Öîä@Òãzé@KåИjJú:–­N ò¹ÏèÕܨ<ø;~˦ýC‰4'“¦9S»V¡Uì€Ð"û+&¶åj£¥Ä6äæ´…2ËZ;\«†VðØVVÙK©¿àÒ÷Uú‘eËÍ,”³ZFz Üiôô‹42–/¦¤â¸õužAS]©ç­Œ˜DÕ+½®jkeÅèñ’öš[(.vŽv…Î#B!„B!„Bˆ»ŸAB „¸™÷DUÂò]ÑXWáÀž8ªö|»ÿdß)•PãNöŸ÷'ª¾?zwÃMÙ¶èT¿¶“ÑÄåT ^Ýà~ÔyûSA—JZ†v øQ=̃¼]±œ±µ¡J‘žÛ‰âr¼iXÓïŠNõä!Žå毻Xf}X¸-†Ó¶ÖT½bX¡w/OÈI!-·àª ÷ÀÃdfp±ÚY¶¬Æ©áÔ¸8òÒ¹ͳ˜½x 1'/ktÁ˜mCžWrñ|ð.ˆzú'-Þ4 ÷)qZZŵ M#'óÕæ-¤Ýß§¸=Ää„Ò±Ž )¥Æ¿\ñ(»BÊŒv*–øœl>}ˆ>“. ›UÁ”œ‰®MWº×YÇwo÷ãH«.txðAUq»TÅÄöZÚh¡ØÞ¤¶PzYSØ9í)Æýž–ßnL­ymΓ¥—½´ú+#®¥avMëÍ—åå•÷*–‘žŠ[Kžìÿ/OXɹ¨×˜Ü³Ì)“¯%Ÿ—ו¾~×rÇèÚêõòýå]—×ަ‰Q®UB!„B!„BÜK¤Xqs)©Û „9¿mæhf&;úS÷ÉvØíùžu»ÎÒR¿…xïÆ<¨¿yÛ¥ÚPÑ£×_ÅÓ0˹©¢Ó£GCUtèô`³YÑÊŸš¦¢+a{µ”Ž/kévÖ (šU-È¥bÄ W – özf-c]iðX$ÿNœ¬Æ/àã@×áE^†«r‚_'¾ËÖrÇG4ÐJɵâJ½Ö ™9éO¶¥ÜGèîí\jE ºr¤mñ¸úøh ó ÅÐ ô¬¦/ÔhìÜ]PŒn<ðÎl¢výªŸ2uè|V>>‘ÿWS ±½–6z#Ê~}eõÀ\c,£ï³¼äF%û`\J){Ye(5®œ+õíMõ^…óâ§_YFz€–NܾÃä:8À¡¿Ùzö:øêÊ8”•ϤÒËi¬\jû¸Çø¥ý]—JòNP!„B!„Bˆ{ŽNB „¸Ù§¿¦- LÜÀºŸÿfŸ[}jûzQ§~eâ7­à÷Ñx5nEeÃÍܶHŽ|ƒðÕ#&ú\9;S̘͙‘‘?ës¹‹îŠ» $œ8‰¥¼c9Âî½IØWÅ·˜¾k_Uç9°ï4¶¢ëB 6篻˜OÛIHÀR¿kžçUåìÆ5Ĺ6£IK]R–cÑ×jÓᱎԪB`•êø:—¿Ãòßüîݵĭœv¡©ÓnþúkÛþ9N¥&ÍñÓ•?ýòÄCÓ®½«XçW…JÆ޶áˆÿÅŸJx:dT±§bÝîôw#º»sxÙ b¬%ÇöêÛè­i ¥—US@-"jÕÉÿ©Œ‹RZÙo@\‹©»üe劼¸ù—•žFú¶é|ù·;½>˜Ì£Á˜;u)gÕÒÏåÍg©Ê£ë¯W¥ø:B!„B]Ò,Ó IDAT!„BÜSd܇â¦Óùµ¦Yåo™»8¿‡&¨×¡kÔ‚J³¾b‰@÷¡§;½Ûjië˜6ôCbæÃAõ°wmI‡ß2~ÞfÚ=I“…s[ÿ$Þ5‹+€¾•ƒM¬ø{«ªw'ˆ³¤º6£YhY¤^“`-ú’éU,´q³GI+Ô‹¬‘}b»öäb—{†½«¾eé¹j<üJcì‹Ë»Ks:w˜Í{óFð™ö­Â<1dœ%§bk†4£[ç F-Ít»´ Òˆÿc?ÆÓyp“žw[ê 6­‹Å­ñ³„]6$ÑP?íG~›ÿ+-CpÑ'’YþîJÅ©9Ý;1já&Òûkú`ÌÞÇéœ"i˜#¹ÿ>?Þüi<ç2‚è44°\w/)N切ÎgHؽ†]§ˆªxõáQ\šÓ¹ýlÆü0šOõÓ¶†7ÆœóO÷§MÛìÎnä×½ÁÞ˜­çÙ<œ]qRJŽ­rµmôZÊÎ/«C‘ÎDõL)e¿Þ}­;ß"Ë~Ž…ì–•ž}Övæ}ñÎ=§Ð¹Jt/<Éæ¡3˜ñkcþ×±"JIç°2òYÖáu1ºYõ*„w£U«–K„B!„BˆH°âæÓùÑâþ:,ˆM¦Ióªù¸¾-iZí+ŽÚ:Ò:DÓ·Õ.Ÿ:Uq¡þàqr8æÌ•…Ę=:“NNžMo‡“‡?5ky“Ãi®iiÑD'ÛáU-c·º.µTâ6oäHº{J„Õt'íh iÿ‰ØJ·†t !„B!„Bqï‘ÀBˆ›êÙ_“ Üåßο$±B!„B!„B!®†NB „B!„B!„B!„÷éBˆÛF#ãàr/ù‡DM¢!„B!„B!„Bˆë'ÀBq3hYœ?¸‰=ñ™”Ø·«%óÏü©¬8œ‹£"!B!„B!„B!Äõ“`!„¸lÑüQŵZsº|‘öÕKÏ_513úðð ‡/cXË‚s––È?ë÷cßàjÉð_!„B!„B!„BÜ w\°%/›ÍviÙb)´^UUTUÅjµ’““ƒ¹`<@Sˆ;S.±s^gÌ9Ôë3”7Â<ÐÎÿÉ÷ŸýÎáó*ª~S§oÂãÑÿñn=/´”dzyѹÄÎÎØŸ­4~t}ª¹`INű¢Èåðìá|°š?ù}ƒ4Žÿñ sG¿Aô5–Ή}{H zš!ÏWÇ”{Šm‹¿à»LøMF]sëís8øÝë|¼Ö‡îßçi¯4ö,ü”oÆL£ÂçÈr(-zBzŽãÅû¼QPp¨`¾-a=£©×³.fi$B!„B!„B!„¸AîˆàììlâŽÄrö̾ýòóBës ??Óݱp–íB]ü½a£&Üß¡z½þªöŸ™™yñw‡×899]WY“’’ -§¤._HHp¡eE¹¾Žm«ÕZhùòÎu¸²ƒ½¬òÅÆ)´\¥Jåš_qÊÜÄÒeqø=4!½ªb´”S,W~@KK&Ms¦v­(B«Ø¡—ýíF–,‹#°÷7<ß3Ël-cK–ÇÔûw DÔŒ ;þi–þ´‘®Ã[“4+ØÔ¡n0ôÔ%Âó »_ý±VêF”¾¾Nð:–­N ò¹ÏèÕܨ<ø;~˦ýC‰ªQrþ(8ôLî• ò+r‹ŠJ¦¿8ìØ„žµd!ÄÝÁ¦j—]ï%B!„B!„BÜ©n{ð¹³g9°o/š¦]wZÿlÙDTýz¸¸¸`g'sª q³éµŒc×v2š¸œ Ô«\ì GÞ•îuÖñÝÛý8Òª |FUÜж1ÄåxÓ°¦EoëPOâXnþº‹3×Q…Ûb8mkMÕb:(t>xëRIË(>ß—¯WOÅŸ“M§ÑgÒ¿[hج ¦äL¬¥ä¯TÚY¶¬Æ©áÔá¿Bˆ»Dfžzñw{£N"„B!„B!Äê¶vkšÆþ½{nxºiiiØl*ŽŽŽRÃ⦲ÙlW=âüž:èò¬eܼ¡ÚPÑ£×—0\ÌX™Þ™MÔ®_XõÓB¦ÏÊÇ'0òÿª£G¥äÔ5®å¶E§G†ª–g½:Z @Ïj—׳‚» Ê µô<”PdõÌZ6ƺÒà±HLr !î9—NœŽ ˆB!„B!„w¨Û6|CÓ´2ê·$™™WLß,DIN:ÅŒß0fÌû|øáG,\¸ðŠé³‹¶ßÙ³g³wïÞÿtÜ̆²çÕùá«;GLô¹’;K{*ÖíNÿwg0¢»;‡—­ Æ :¿ªÏs`ßilEÓ %Øœ¿îb—„í$$`©Žß è—×ùU¡’1…c§møâñ§žŽºRófÌfÈÌÈ p_³ÊÙkˆsmF“Òý+„¸{$e^º.{ÉôB!„B!„BÜ©nËð„sç8´?¿ãlÉŒ©…Ö9+…»QšÔ (´\ÑÛ³ÐòÚ]» -ûN¡åO'|†Ñ`,5?111÷ðð(´îøñã…–‹>#¸è²Ù\ø Ñ¢ÏÄݹsg¡eOO¯BËgΜ.´ìíí]h911±Ôý»¹¹ާ³s‰eHII)´\êz®p¯š¦îÚ:{öl¡e›­ðúðð°BËqqÇ -ÛÙŽ_ÑNØúõëßðöh³Ùøá‡¨R¥ ݺuÅf³‘PæÈÞ¢ÏSþ/r4ëIÏ)=ŠkK:´ø–ñóÆ0ÓîIš(œÛú'ñ6¨ ¨g6òë^À`oÌÖóì?žή8) ¸4§s‡Ù¼7oŸiOÐ*ÌCÆYr*¶¦aH3ºubÔ‚ÑL·@« ø?fðc|07Áá”OqiNçö³óÃh>Õ?NÛÞsÎs<ÝŸ6m#p(-Á•¨lbÅßsXU½;Aœ%ÕµÍB“Ù´.·ÆÏ&ý¿Bˆ»ÈÑ„ÜKïÏ*;I@„B!„B!„¸CÝòàœìlØwËö—››‹^§G§“gÕ‰âååå‘‘‘Add$^^ùò>>>×ïÛ·U«V“››ƒ¯¯/=zôÀÝÝ€%K–°dÉ*1`ÀSÿ¹Ø9˜t(ŠRúh~Å…úƒÇñ¬Ý4–Íͯ&|C}Ð+ :ØR޲ù‡EÌ:“†Eï„wµfôÚ›`=€ý'ð†Ët毚ÈGßçbp ¤ñ€z4©@hßyÃ<•¹‹Çð^ ¸†4¦çÈçéz£F¦9Ró©î2…¿MfüÜLpð&¨ù ·À¡Œü5éÿ2&~Å‚±ë°:øS·o8íײ)Þ“†#0Êá'„¸‹ÄžÏÀ¨Wh\E:€…B!„B!„¸SÝòàãqGoêÔÏÅÉËËÃÎÎNj[ËÎÎŽ€€V®\Iûöí¯Ì3Ï D¯×³téRÖ¬YC=èÒ¥ µk×þÏÞ` SÀÅNOj¶µÔíûª´}n"mŸË_VOÌæ—Vâæª` ëË;_ô-å,åMGFRç‘â2àEä#£ˆ|¤¤ Vþöî;Ίê|üøgæÖݽÛ{a–eéKïUQ!‚%Hl_cbŒFQ£I4‚-¶˜ü’h BDEAA¤w¤³líýö;3¿?îÂ:Èó~é‹=wæž9óœsïîÜçž9Lzù+&ýXØhúxô¡BKÛëßkÊcôšrâí3§\Î3.玆Gtö¿»„‚˜aLë$é_!Ä…£¬6@^…€áÙáØ-ŠE!„B!„BˆóÔYO—<ë'ÐhšÖâ-}ÅÅIQ&OžÌ²e˘3g¡¡¡Œ=šöíÛàp™åÔ£GV®\Ùð%UU1›ÍuübÂLÔx´&¿ØñÕs©Ø½bbˆ ³£xËÈ]¿”ܰ®tßý9_å^DÁÒKXóÅ>BÚôcÿÂÿQpԦ˯ø‰¼…ç­%;k1 °YTn'B!„B!„âè1µÆÜ ÕTŽw¤•ã­kn”ÔÝ»wgPyíÚÕ$&$ѹs—ÛØx à‚Ö›6¯9øàC¦N½¤¤$6mÚÄwß}GMM &“™˜˜hyç8zŒ¨ 1¡&ÊÇŸ¼g×6ÊvlfçÁZ<¾†ÉŽ#&•nÝpîÛÁž‹(VFÍv¶WÚ‰ëPKî®àõ¸%,„8_9½:kö9¸¦W1af ŠB!„B!„ç±³ö žaèšvÎN´¸¤ˆ¬¬,,«ôºh’ÉdbÀ€lÚ´‰ýû÷ÎçŸΤI“hß¾=[·neÅŠ@ýÌaíŽéóIt˜OÀÀé=6wþòa ÐQî’!.0sÖUâöëômÆÏÉì_!„B!„B!ÎwgmáRý6S¦LirûÀ8pà1ÇÇÇs÷ÝrCß ñ£@r¤…J—B¥SC7 ŠB\€¶póÞê ~Ú7†ÆÊÌ_!„B!„B!. g-|>¬•ZUU%=.ÄYj&Ân¢Â©QãÑ0$,„·Oç‹ïk0©ðêÄ„™%(B!„B!„B\`ÎÚ§zúyp h·Ç}ÜÇÏŠ=Zã­-‰ŒŒlv{xxø)Cjjê‰u°ÙÜlÙf³5ûüÆ3˜kéù-IJJ:¥xŠó—IUˆ7ç0ãôé8½Þ€A@3df°Bœ'  À0¨pPT…_ŽJ”Û= !„B!„Bq“iBˆ3JQÀaSqØT †B!„B!„B!ÄvÖÀf«]×é2ÀôxÅÞ¸ ²³¼&¨œ_œ4š·)x áˆØà5eãÁ·šv„™qÛx}Y!„B!„B!„B!„ø19kSòTÕtÎOöTo¿,„B!„B!„B!„ç³³–n¼öì¹ó£è4]×¹ñÆ›xùå—e !„B!„B!„B!œµ°Åj9ç'÷ãè4U媫~€Ok½=ö©©i$'§Ð±c'®»n2+W®”W‰B!„B!„B!„ˆ³6-×nÁf³ãUF=nU·•¨**k1éAå¸ðØàzÃ"‚Ê^ ¨Ü»w“““47yòäÓ^gMM-7Üp=>ú(555Ìš5››o¾…;wÈ+E!„B!„B!„Bˆ €z±œhxxx³·¡ž1c½zõ¦M›tºuëÎ+¯¼‚ax<~ø²³;Òµk7f̘¦iAÏÿôÓù <¿ßÀ+¯¼ÂÔ©S¶ÿüç¿à…^l±¾W_}•>}ú’–Ö†«¯¾MÓŽûØ´iÓøÇ?þ €ËåâÁ¤K—®ôèÑ“ÿþ÷í†ãΜ9“ž={ѦM:'N"8’ Œs3f<×ð˜Íf'22’6mÚðË_ÞƒÅb‘W‰B!„B!„B!„ˆ³º0¯¢(çìD£££šÝþÃ?0qâD¦M›Ê–-[¸ë®»>|8999<ûìtÖ­[ÇܹŸàñx¸ë®»‰‹‹ãÖ[omx~ß¾}())¡¤¤„””Ö®]Ç÷ßßÞºu+Ó¦Õ'„›«oýúõ\{íµÜu׸ÝnL&Óq;Ú_ÿúW¶mû/¾øœ~ø_þò^ èOJJ O>ù¯¿þýúõ£¸¸8( ®ª*}úô!+«ý1ñðz½Ìš5§Ó)¯!„B!„ø0jóÙ¸y'û]‰Œ¸¬ÑÊÉí#„B!„âüvÖgwíÙÿ¬Ÿdxx8¡¡¡-îORR£F¢k×.lÞ¼¿ßÏÇ̃>@çÎéÕ«wß}7³gÏA×õ çfee±víZü~?;wîÄjµ’››K^^>N§“=z´ª¾„„bccIKKk¨ÿxøý~æÍ›Ç}÷ýšôôtFM¯^½X±b&“ «ÕÊŽ;±Ûídeew¾ªò—¿ü™k®¹¦á±·Þz‹ÌÌö´k—É‹/¾È3Ï<-¯!„B!„øðm|—?<ý7þñ¿­T'¿B!„Bˆó›ùlpØ%£Éés$ üÇéOmÏ÷–•Ó-!AeM ¾ q•Ç^90¨Ü©SÇn£ªªDFFár¹ðz½ÔÔÔ%^Û´iCYYš¦¡ªõ9t“ÉİaÃXºt)dee‘––ÆŠ+Q…~ýú‚Óél¶¾åõz©¨¨äÖ[ok˜am$44”ýë_<ñÄüãopß}¿ág?»©Ù™Ø×^{-÷ßÿ"""ˆŽŽ–WˆB!„âüb³à¹§ywsUµ.<š‚-,†”Ì. ¹r“†¤a¿ÂPù ¼ë%–×)Ä_ñÿïž^„œí68÷³äã9|ºl3»‹kÑ,‘$µÏaø¸Ÿ2qpBdö°B!„Bœg=l2™ uàtÕ×9œ$µÙlDDDPPP@§NÈÏÏ'66¶!ù{ØèÑ—sソ"))™AƒѾ}&ï¿ÿ>ºn0vì(ŠrBõµ†Íf#**Š¿ÿýu tÌöþýûñÙgóY´hwÞy=zäУG&니ˆ ##C^B!„Bˆó“á¡4/ƒ:æÐb"T|uåìÛ´˜½›7‘÷à‹<2,ŠwîQcßçsYí4ƒ²ÅsùzJOÆÅ½³6*×ðòï§3?ÏKÃ×´}åìßü ooYÁò«æ™i=‰”$°B!„BœuæsqP›Í†®ë¸=®3zœììö§\‡ÅbáÚk¯áÙg§“––†Ûíæµ×^ãöÛqÌZ¼={öàÍ7ßdΜÙ$''ó»ßýŸÏÏôéÏžp}­mߨ±cùë_ÿÊŸþôg(,,$''Ã0ظq#:u"33“°°° 5}u]çxŒ~ýúÝZ!„B!.„ËÙœÛ^âé±Ñ(2¾šþf,«bõòmø† Ʀ`É›3ùtýNöUàÔ¬DµÇï§ßD7µ’Msßæ¿Ÿ­fg™ŸÄŽ ½ú&n»"›pªYðÇ_ðüZ?I×ü…ý¼3ê¹ïî·Èí÷ÞýýpœKxâ–XîKb⳯r{'(Yó>¯½³ˆû«ÐìÄ·ÊíNc`¤Z>~‹·>_Ãî Grg†M¸…©£ÚÕÏ’m®­Ç»j÷lä“/rÑÌíéÛ¥œu›63÷‹½Œ¹©}ÐE¾áÜŧÿúsVì¢ÄFj¼A€àµ Z³Ï1Œ¾û¯2?Ï a¸êŽÛ™Ô?Ú},ýà ÞX´Ÿ=ó^åÍ>/ñ«^¡ åòéKÿæó…”VÕRçUp$e3øÚ[¹}t{B­ù !„B!„8+æs$$äÌÞœ*;»=mÛ¦Ÿ–ºxà\.7W]u5f³™[o½…›o¾ù˜ý, W]u .$++ UUéßš¦wÂõµÖþð(O<ñ'NÄétÒ¥KæÍ›Kuu5¿þõ}ìÝ»‡ÃÁ„ èßÿÈí·u]gíÚµr«g!„B!ÄLÇ[[I¥3(DÇÅ`Ð Yµ`›\`‰$Òæ¡ÎJ”âbÓ¿ÿÀ#sóñc"$ÔJ]á&>}m»kžæ¹ÉíèÙ; óº­”îÞC¹Ñ‰Ð?«xwï`O`8]öíd·Ï@‰Ê¡o¦ £b!/?;‹UIé¤Ú\””û±†)€›Ío>ΣŸä°Dg¥º`#s_Ê¥Êú Fiª­ÇÍÂT.ÿ‚¥•`ï=–{'åñ»-óÈ[¸€ï¦ïáû_å,|áI^YYªÈ(¢µè•ÜmÍ>ÇkAå æ¯¨ÂPlô¸éþïÒÄú׎®Œ»ç7Tîû oï)eñ—øE¯!„ê¥l[µ™].°†F⤲p3Ÿ½ú4jÂ+ÜÛËŽQñM31B!„Bq"Ìçòà!!!Ìøã <OÃck¿ý6hŸåUAåPGDP9§G· rûöíNª-ÿú׿‚Êo½õfP;Ÿyæižyæéëyøá‡xøá‡Êo¼ñ÷ãžwSõ5nGk cúôéLŸ>=hŸ„„–.ý¶éÎ7›Y°à‹†ò‹/¾ ¯!„B!ÄÂÏúWoeÌ«‡Ë æäÑÜ}]Çà ]%‘«ÿøwtV tL• xù³üJ#ï†GDRôéÓüêõìøä#VŽÿ-Czõ¦i+»ömg§g4ö-;ð`TþÀÖbwì¤BWïчÎVÐËrÐg ØºsëÓ`|œ # a˜Á([»Ÿå°tâ¶ŸbJº™Ò⯬gù+(v%qM´Õ|¼,¬^Ä×_lÄECG $±s&—¤ÍçÝüòFúŒŒ¤~Rñ·üom-†)…Ÿ<1{z9p~ý'¦<¿ýpU­Øçx´y 0µ¡O¯øàÛm›ÛÐ;'wöÄ[X@±íŽ:¿Ÿ<ñwd—3ï±_òê¦2V®ÜÉݽrPš‰¡B!„Bˆ£žëX,ÂÂÂ0›OýªÎá“B!„Bˆ‹‚‚#¹]»t¢cÛf-äù—¾¦È8þþf³‰ÀÞìò(½3$3VÒ.N7‹‚áÜÃö 5¥ýÓLî]lÛ³‡ÍÛjQÃÂÑòùþQ7Ä> IDAT‡"¶mËCSBèÙ¿!€)½R,àÙÈËwÜÁ}ÏÀÒB/ Ø»ƒ>÷ß=‰Ñã¯åÆ—×á2 ´Ò"J´¦ÛzªÊ«ñ j3o»æÌŽt°,fCÍz,+¥Çˆ(оù–-~%,“ì´úëËöƒüÑÇìùf1(éÜ4øwÈwk¾f¹öƒè~(í¨£ZÍdÌm3æú\fþþ7¼µ#—ërÈv´5-a[ ü0®›A¨þꪭñÄÈWï÷,\ZŒ®˜pÄ%e;¼AÇUVD…g‹ïcÒÏÚcÎȤy1ß׬eþâ"z^~lF¼å}, ºÿ=Üß8Þ•Œéû.—×°é¼~û¦`«Ëeéû¯óÁ^ Ceب^„¶öÜš‰áґߤB!„BÑzçåj:V«•Ääd““ÉîÚUzIˆóX…3À»«ÊY´­ŸfH@„½:¯FWc_™—E?Ô`5)\Þ5’)ýcˆ “… …âô °å­û¹y–‚ßYM¥Ëa(„ç\ÆÐdõ¸³N”ØL¹âlù_!‹gÜÁª×­ønØÈúÉ5 <´²¹ý`†&Ïåý SZoú§G?°3ÖµðJï¡}ˆ8œÿÝ=›ûúw|"q¡~Jöë „’”…št S.ùŒ'³üõ_1é­ÂµN—=þoîïÛúÙ­® ‹YVi Øûp׋pyäáì±ÆŽÿü’_Ï)$ÿÛ%츾=]/aÊ¥óyâË"–ÿíÿ¸î¿QØ|UøÃ_¡VZ±ÏñƒÍÈÛï`í¾YtpMÿ m·’>æ.¦ö kõ-œ›‹¡ÜZ!„B!NŒ|‰VqR Þ_]Áíoåòù–jIþ !D|šÁüÍUÜþV.³ÖV ï–BqŠq©)Ćš8Ë))­ F %1³cnú/>r)Í]é*¡ôšöOýl8ì<¡I]óó'øË h˜TkjÏ¥#30+* }ûÒÞ¤Ýoݬ Jä.ïјÔt©møËóÙ[†בK~ö;þoxŠÉÀÿû3OÝ| ÝS#0yk©õY‰k—E‚Åßúß F«¿YK¡`ï1„þG§EMd Lš ôâå,Úê%œþwý‰?Þ4‚nI¡j+¨ ؉Më@¯õÉëÖìÓÔ‡ qCyàùgøíÄ¡tK‹"ÄbÂMz·áÜpÿ³¼xw?¢O sÛl eÔ !„B!ĉ]:_>éŽCכơÿ 0  CǨÿÝÐ1ôú²®kºŽ®ë‡þÕ¨­*cñç³%¢B4aýúµŒ;þGq.n¿Îs ŠX±§N:V!NÐà,¿DÈü¶Ð{%<*U5¡¨*ªªúׄ¢(õ?+*(J}YQQêË(ÔÚ_ÿ‘¿¢ÈGÿBÆá4¡\· !„Bˆsk䨉r½'„ç9™,„8!¿ÎïfåKòW!NÒòÝu<<§o@æ !„B!„B!N?I !ZͦQÄžR¯C!NÁ®b3”ÛA !„B!„B!N;I !ZíÃ5¬Ü+3…âtX¾»ŽYk*$B!„B!„BˆÓÊ,!B´F…3À‡’¨â¤(èt`#ÙÆZâMQê@ {éÎVe0^B$H©×T0ºk$Q¡& †B!„B!„â´°¢UÞ]UŽÇ¯K D#&ºæDÓÃ]Íû»üühFˆb¦G(²ªªø(7pJ·éím|Šѧ]f–øpÔ †_'PñåyÏ2·pŸ1 Ë…wŽŠ™AƒãéSUÎk[}'>3ƒ‡&2¤¦”ç6ù¸ØÞeÜ~wW•s÷% òv"„B!„B!„8-äÐBˆ¹ý:‹¶Õœ‘· Ää0zƪ(çuão;7ߨž7†…bm.®Š™œnÑŒH0½xÓ¶3@±Ð¿W4CãN~)†Áuêó<Ò{FÚ°eÄ¢†ZAQ@QP¬&,I‘$õeÚðÏøýnì8Ï^Ÿ†s<<²3t‰Pêë9áþ1‘Ñ&”,ÇÅûšýj[µ|ÁF!„B!„BqÚHXÑ¢õû]ø´ÖÏTB#xôÎlfOˆ"¡¹ŒŽÉÎOÇ¥ð³ŒÓxëÓ3QçÅÈÐ)¯òQP§ÕÏÈ<qU¬L¾¾ïŽ ktû …öƒÚ2wjÝÕ“hÛyêå&÷ù {ÛXZÊlšcÃ0¼‚;¬¿G1Œר-ò 6ä¹$B!„B!„BˆÓBn-„hѪ½u'´z×(úh\)QŒOªæß â…Æð1~>ó¥m'ÞÒö“nSóíMížÂcCI´+<>6_Æ«+”n²b¢[¯xnÎ £C¸ŠáÓ8XîfÁ·EÌ+i)VGâÜ{T;Oªæ¾÷ÊÙ«¦0îšBòª}<²¹~ ؈.©¼9BçÅÿT:!‘ûó¹{™­©¸îª¯=®cotµo5¨(­ã“%%Ì+ÒOn]ÙÃýÓÖJR¸ ;åGשظþúFmSÌäôŽã–™aà©ó2oAï4@1Ó³o<·t£](T–:ùly)sòGf¨š¬ Ï BI±êyñÛÀt»Zçzzu!…5&™Ä¿ÿý/œÎ#³;í6•p‡Jiy€1cÆn7X°àˆíá‘ߣš] ¸ÙqÒRÌZ{Ž­=^³ýרZÓ¶ §‡pó„T.w–òÐüj ´‹ã}vw‰W~Ù!„B!„B!N I !ZTZëoõ¾‘™Q £–gwxØáªcÊ‘ ¯e~“`ƒ½ëðü¶:®ZPé:$•‡³ýÌþö¯×šèÕ/ÛÇÇSüv1ë#¢¹od嫊x(7€j%¬.pT"éxuÍDzjÑ¥e<󕇀ÝÎøáqÜ5ÄË+J™¾Â ªm wNbJQ.ÿ,<µ6©qÍ··ê`5ÿYPI¹bÓ¢¹}PÓJ÷1}·ŽBö€4žì£°zU Oi(ááL½,œŽá@IóíZë;“ù|ìdÛa¯ LÑv:Úb“íX7×áE!+ÙŽR\ÎV¤¶ª¯êy+똹ÑE¹a¦_ßx¦‹§àíbÖùNfÄêŸò2f|íÅg±Ð¿OÜ‘:Ž ÙRy¼,_UÌÛÅ–0ÎjPè8(•Ç{ÀâåEü§2:ÇrËU©ØfçñNq}ßöžÊo;ê,Zq7* bS"™œttr´µqEQ=0…uë=,^¼8(ù ðÔý)ŒIçQ[‰ŠŠbÖ¬YAÛµ7°Äµ9&1Úü8i!f¾Öœã‰ïÌô§b²rŘdÆéU<±àâIþÖ¿Ïä—B!„B!„â´°¢E.o+WòT,ŒìJÕö6û!°¿šÅÎ4ÆvµñÅJ/Mår|N¹åþ†¤’bwpM73ë¿Éç½]°g±™>·Ä24¥„èl(p³½T¼-ÖyÜóªr³±Ðƒ†›Ò¨púõñ²âû:ÖkÀèÖ)…®)ÔBÆ)´Iµ7ß^g™“•‡~ÞU¢‘‘ÁeÉVL»=l®íi¥p]3Öz J¨…kŒðVÅjm ]ì$–nÉ*_ìщI !ѯa$‡Ð^­c6rRUöÿà¢ÒPZÕW‡×¶­-©ãÛ}õ³q·º¬ô¹.‚~ñ ë Oþöß®J7ëòëëÜRg¡×á:4ÚÑæ`BO+û×ìçÅõ¾ q¦ØLȱ²oÍ~^ÞäC6ð›Á„Þ>þ¼WˆƒñÍìX‘ËË›[¡Nf—0º´rLg€n1¥¼õý÷ǾD54œ¬L©V>øàƒcöÉŠqQLn"Z?NZˆÙúŠ–Ï±±Öï´ö§jfȨx¦E:™>§ŒmÙ„X§O“_6B!„B!„BˆÓBÀBˆµ6…gŠàòD Jinþàe|—:­)ek+ó¦(m-*‰—eòñeG=n_˜Š¶½šÙù~~mvT3s5+J5N%}RYÀ°š‰4`(wA7›Š¨§Ð¦ÀÁæÚ«”Ëm}tŠ6cÓ4üЪõõGÛho°:Ïwܤ[K±RÐúÏp9YSÏ5mìXöxéÖÆÆŽõ•x{‡“ Ûµº‡ûX›ëGÇzÒ±Ôk|” a?}cP¯ñQÜDæh;íÍVø¦h;™‡¶5|A÷³å@€ÚÚHUkÙe£êgÅÁ@“cýDâ 0 ‹…~=ÂX³ÉÙð˜¢À=·$гK(/ý± ×ß³·çÈ,ÂÃLÜzUÏó‹æÇIK13µâOõx§ÚŸñ]“¸Ïìg·%¬r]„ï³²TºB!„B!„â49/À†áÆãYÏ·¿?šVŠ®×ˆ®ªa˜LñX,m±Z»b·÷FQìÒ“Bœs ºDa¶pÛÍÙÜô¢6sY›r¶æg&±qܪÀ°xa!ïàqišÆÿææ².=‚q½£ùõõÑ\µ¢G×xð5Ug Zýí‚U¥áˆ€^Ÿ´SNµMš·ÉmØh…¾µ„–x©4,ŒÌÀáPT 4½É°7ß® XûY—ëå–.a´·©ôIö³~Y îÔF¦[øF£m­“7šZ䵕q5 ÐŽåQø5°Z§íV4£ÉÛ7]' ³Oþ—N}%ŠÒìðn}œj,_½•Å?ª`÷~/Qá*Äsé †V¿÷O.‰dã§™·°Šüƒ~b¢LLùI4‘•.Ü…Ž úÔÆI‹1kÍ9žâñNì÷ü±ýYs –Ý1üä’XÖRÆVYW!„B!„B!NÊy•âtÎÃí^†aáH]¯A×kðû÷àr-BQ¬„„ !,ìjÌæ$éQ!ÎK(£²ÍìX]Àë»Nˆ™zi*Wv ãß¹µ-lèxjSQ¡aö¦Vå%_‹"3 жûš¸Ý¬Î¼*þ™Wòaé<Ý#’.ë=ll¢ÎSuJmÒšÞ¶=ÎN†âæÕ•5lôŠÆÏQÇ­öqÀˆ¢s²¥èØ[Z·®]Gî®cÿ€Fö2‘ãròQu×~/·µdŒn§lO{uŽMªžŽ¸ò*t¬éatµÖ±áðÛ¼ÉFŸ43þ /wìVôMžE×4 ¦¢à[@k•öê·©Eõ·€FµÐ-ÅŒ·ÌK¡~dŸéV̽Çá‰Æy‡¯'£ßs÷ĘúS7àŠ{\üím'i‰*&ʪ væj¼õd4Û™8¼ã’]ÙRäÖÆI‹1jÅ9žêñåÔ2ñÞòjž]ìæ¾‰‰<2ÆÏŸVs@—·V!„B!„B!„8QêùÐÃðRS󥥿Ååú¦ÉäïñŸëÃåú†ÒÒû©©™‰aø¥W…8ÂÒÃds³ä{{J½ìmøßÅ—»<Ø3"ÚèIºŸ=e)cŸJÏ̆'›À]Ç'[ý¤õIæþôK ¡g»®êl'P#Ã×ÕA·DY)¡tQÁ£Qk4]ç)O=…65·Í_áåv®èA¯$™ñ6mG×UÇü]ú's{·0º¦„qi¯pÚª­kWczUß–XÛ7œº½uìסxoû£ùiŠŸïv6‘<-q5Ø´©Š½ö~{uWwt0 C$·ŽOfB´Ÿ…ë¨9…¾ÉìŸÂý}Ãé›BÏö ŒS0¼u|´ÙG»~Éü²GÝÓüdT2×Åú˜·¾'`x똳ÉGZß~7 ‚>i!äd„ji}ÿ7¶Š+¨ÜãB¯ð£Wø1*ýüíN3q!K×X¸"@u¥Æã7šé©7ìçÝ[Î×Þk©¯¥qÒbŒZqŽ'}–-u±ª,Àö…<ˆçæ>É< U¥N>œWÊ좆Ցٱ²€Ç<ñܔϣýM¨šFY•›å‡Ûtbqv*Q¼_q+Óøf[}Š8Ó ¯ÝÞh„Ø@!€V|è,ô+K;³‘KŽ~‹ã¤%­9Ç“<žágñêJ\É´nulXvj¯4OQϯ å…A‰üt_>ï•Êâ¸B!NŸoŠý¼¹ÇÖ* W@~Ljs'Ô¬Ð=ÊÄmííŒL´œµãz} ¾'Èf5K„BqÁQ.ŸtÇ¡«^ãІ†¡cÔÿ€nèz}Y×5 ]G×õCÿjÔV•±øóÙ'tp¿O£ë5§õ¤L¦bbÂlNoÕþÕÕÕtÏwìÌc‹ÅÂêÕ«q¹œ¼ð |óÍbjjjHOOgÚ´©ÜvÛm(ŠÂž={Ni{~~>ƒÁïžÁü—¿ü™©S§¢ë:ÿüçÿãÍ7ߤ  €ÄÄD®»n¿þõ¯±Z/®9R ,`ÕªÕ€ÝBZZ*ƒ¦mÛ¶§ýXË—/gÓ¦ÍÜqÇí¨jý”K§ÓÉ /¼ˆ¦Õ_8›Íf~ÿûß·X×úõk;vüóq/í”wËóˆà 7D²yv.ÿ*’.ÏÛ_°ܨ<Í8ÇX¬Žf÷Õ^¾¯Mf†þ."$x©ù¿ÊþQOçÞC ŠCUM(ªŠªª‡þ5¡(JýÏŠ ŠR_VÔúuª…£`?õ[Œ ñc`G¾¸t¶¯Û~,^Üîæï;=2˜ÄyçÎl;¿îrVŽUSç%6F¾0/Dk•WTá°I „hd䨉r½'„ç¹sö¶@ èŒ$4­‚ŠŠgˆ‹ûK«f+ŠB›6ix½GÀ¸\.BCC1 믿¼¼<, 6›={öðè£ !!‘Q£.=¥íãÇ£¶¶Ã0 !%%¥¡‘‘õííµ×øóŸÿ‚Åb¡S§NìØ±ƒ^xÇËcýᢴ‡^½z2jÔ(\.Û·oç½÷Þgܸ+ÉÉÉ9=à——óå—_’——OxxxÐ6ŸÏGxx8wÝugý7òGŠ8£2²¢è¤ø8P§¡Ûl ìMzu5¯ËÌÈóšÂLýav׿0ÅòI¶:L–µ~½_ÃÐÑ^꼟y'2ϸ¿",!„gÂâb¿$Åyëï;=ôˆ6sÉYš )A¢•Ê+ª$B!„¸ “°ax©¬|îŒ$«OÏ 6ö ¥ùÓŒˆˆ`Ù²e åƒrùå£ñx<<þøc¤¤¤ðøãsà@!7Ýt&“‰É“§°bÅ ¾ûî;ÆwÊÛ'€ ÈÌ™3ƒ’Šš¦1wî\¦O–É“'óá‡ò«_ýšÅ‹_t `¨Ÿ™Jhh(C‡Åf³±páB:uê„ÕjÅï÷³`Á~øáTÕÄÈ‘#èÓ§@€… ²iÓft]gøða 2$¨~§ÓIFF=zô`É’%AÛ¼^/!!vÌfsì`!Ε„d×f[‰QQýr ªyva9Û5‰ÎùÎPV2ž5þ+èä_E'Ö§Ä„FµÍ%‡ÍÊ\„ƒ|—D!„8cþ³G’¿âüöæÏYK !„B!~üÎI¸¶vV«×ü-)ñ³iS%%õ³s¬ôìé >¾å #¿7uu>é„Ú÷ /PVVÆÀ˜|ðt€B!„ç1gàÂøÆUFh>×·ù›ê•N“1*„B!„'í¬Ïv:ç-³ÕçÓY¾¼†C“eƒè:,[VCr²«µù¶ax©­Cdä´VµoÖ¬Ù¸Ýn èÏÀƒ¶išÆSO=Å?þñObccùÏþÝ0K÷T·7ŽnݺSZZÊš5«yç™Ìš5›6mÚðÀPQQ×ë%11‘ŒŒtÖ¬YKnn.sæ|ÄwÞqÑd¿ßÏç#$$MÓp»]‡³Ê¡q`‘‘¦ixÊ ´päSýñ ާOU9¯mõó>8£±ý±½~÷ÝyÖ—B!Äé2:n1“Û®&&ÄÁÔÌyìu¦“ëJ—À!„B!„8agõÐÏz £u·²r:µÓ²ÔÏöx6¶¸_^^;wîÄb±0jÔ¥AÛfÏžÍ̙˜È¼”¼=Ûsss @ýlÖ­[·ŽË墴´EQ°ÛëÞYYY¨ªŠ×{qÞÌçóQWWGii)K—.eáÂE\vÙ(l6&“‰Î;ñõ×ßPTT„ÓéäÀ†Ñ°í›oê·ÕÖÖRVVvBÇÞ»w/eeeTWW³råJÜn—¼“\°ï€vn¾±=o Åzè-119Œž±*A«:+frºE3"ÁÄI­ö¬˜é‘Í%‰'ùüsÂÌ þñ\• Pw&”Š…þ½¢§¶“Sÿ¡:²3t‰P΃>8ñ=ßœ†×OPßW})„B´âzTWÐ ºß„æ7£ù­h šfF7T T"LÕLLû’˜3ŠšÀè”Ü“õñ¶²£/l%˜B!„B!Zå¬ÎöŠLû IDATù¾oõ¾aa¦Ó²ÏÑÇ Ôì>6l M|||Ã㺮3sæLt]Çårqà 76lKLL`Ö¬Y§´ýý÷ßçºë&SRRBtt45558NÌf3?ûÙMDGG3tè¾þú®ºêj²²²Ø·o~¿Ÿ‘#G\tƒÖn·³jÕj6l؈Ýn'55…É“¯#33³aŸË.»Œ/¿ü’ÿþ÷¿ø|>¹í¶Û0›Í\~ùå,X°€·Þz‹@@£W¯ž\yå•­:¶aìØ±ƒ-[¶àóùHHHdâĉòNr¡2tÊ«|Ôiõ³Mv~:.…öësÙT~qÏ+T–j°ö n)[!„Bù]®ë¨†ŽfjúrÚðh.šÇŠæ³¢ë6t“Lf0©`ÕÁª£X(&#âW04a+ŠÚ“93·áÖßç¹7Qç £ÛØHW4Å, „B!„¢YgõÊÑïÏoõ¾iiõëû6uh›M%-ÍzÇÎkqŸ;vbíÚµÃl>MÓ(-­ÿæuMM 555 Û|>ß)o÷ûýôìÙ“µk×RZZŠÍfcРAÜsÏÿ1jÔ(^ýï<ýôÓ|úé§lÛ¶ØØX&L¸–Gy䢴cÆŒa̘1ÍîcµZ?~<ãÇ?f›Åbir[c:t C‡ eEQ;v,cÇŽ•wÃÇüùùÌ—H#&3œ.~'Í—ìJl…BqäïGƒ+·­#?:ŽÍ©íŽÝîÔÐK ü•f¼n;º9l°[ÀjÕ (€ŠªÙ¢0"a‘v°ºðP±ZR˜Öa3±xG6¿ðÏäi~ÉfºH?ˆ³®¼¼¼Õw 3NÓlõS©'--M:M!„B\ÔÎjXÓŠ[½¯Õª2dHK–T¡7úŒ\Uaðà¬VõŽ]Ôâ>?ü?üÐ1[,–/_ÖìsOuû?þñF³Û###xæ™§yæ™§eÔŠóBïQíx<©šûÞ+g¯˜Â¸wj É«öñÈæú5^#º¤òæ¹»C˜pú÷}ñßù¶ñgЉn½â¹9'Œá*†Oã`¹›ß1¯ÔÊõ×§3r>w/ó :—NÃÚñé0ƒeŸïæ/»ê«Šë˜Ä]-Ä[ *JëødI óŠt S(wß–Jûõ¹üv½P"¢ùëÍÑä~²× ½Ž3âx:ËF‡pg•›E+Jxg“a5“Ó;Ž[rd†§Î˼¼wÐÅLϾñÜÒ=Œv¡PYêä³å¥ÌÉÔÏfVÌ žÄäv6’*6tŠ j˜»Ç [çpzÆ›±x}¬ÛPÂ+ëÜÔ6:îÀ!¸s°ùPãR»§ðØÀPí Íß—ñêJ'åÆQÇjk%)Ü„ƒò£ã`²2lp<7t %ŪSTäÅoãй+ô»<“G㫸÷½röúœ'£/usó‡·ª›ÿ¡67ãÄö¤â£Zè×?Ž)Cië€ê's¾-á³b•ÌnñÜÙ×Av¸JÀígÕ²Bfüà?²ÆñqbÛü5µÛ35š=ׯZ>÷fû%„Bœ;½ò÷qËêÅÌê5(8ì`Îwà ¿ÓŠaµ`ØM`QÀ¤€ªÔ_È* ¨˜ôCÿDتèµ; ÝêÙfŽá61´™ìý+ÙímÇvw>¬Òâì^ñ) ªzä3˜æ’³Šrr u4®ódëB!„Bœå°aœØÍ.32ìŒ˦Mu””øHH°Ò³§ƒ¸8Ë Û#½-Ä™{u³#߃¯“l;ìu)ÚNG»Bl²ëæ:¼(d%ÛQŠËù:Oa|öéßw«ï˜ÈÆ“}V¯*á©" %<œ©—…Ó1(=þ¹ì]€ç·Ð1pÕùÂ[YÇÌ.Ê 3ýúÆ3m\<o³®uË‘c5ilX[Ìûµ m:Å𳱩X>ÉãÇKQ*dHåñ^°|U1okXÂL8« @¡ã Tï‹—ñŸrÈèË-W¥b›Ç;Å`"=5„èÒ2žùÊCÀngüð8îâå‹¥L_aÕ6†;'1¥(—9OÅá`X²Æšõ.¿sV¬æ? *)÷BlZ4·JbZé>¦ïÖ1«¼Œ_{ñY,ôïw$>>•ÞÃSùmGE+òF…AlJ$““'€ ¶å¹ñu ¡[ì¯0Ñ)ÅŠï@9»uèÖ\ü}­‰Gc'•®CRy8ÛÏìoðz­‰^ý¸}|<Åo³>"šûF†Q¾ªˆ‡r(¡VÂêA ÌccÛÒm)¶gj,4®k½ÖÔ¸–ϽéþkáXy‡Bq~õz¸vÝJ:å3d_$‹:t§Â‰¥°ÛΔb ¯9ìv EEQ!ÚRIª­ ‹]g·Ú‰5º>¬rè_d{1íBóP”¨&þ† ¡k{JH?-XÀÊŠÞÌw]&"„B!„¢IçýâAññ.»,ZzJˆcèz€Ò¢B6o^G]u9=ºu"1¥=¡QÉÔYZ9ôÿ™ç*t±“Xº%«|±G'&5„D¿†‘B{µŽmØÈIUÙÿƒ‹œ‘}+çùl®íi¥p]3Öz J¨…kŒðfÏÅçô‘[~ô¬ÍújKêøv_ýLá­.+}®‹ _¼Âº¢ÖŨto%n­þú|/Öè &å„1³ 'Ƕ}BO+û×ìçÅõ>ŽÎ1+6r¬ì[³Ÿ—7ùÐÍ|„Æf0¡·ƒ??RŸ«ÊÍÆBnJ£Âé×ÇËŠïëX¯ [§º¦XP } 3eÚ‡ÓÑëäÂ#u–9Yyèç]%Ù\–lÅ´ÛÃáüœ«ÒͺüúóÛRg¡×¡ø¬¯p0¾³™+ryyÓ¡¸êdv k¸¡+¿Žïµúg˜øl«†a¶Ó5Á`ûrLÍÆ}Yëãq̸=øv×t3³þ›|ÞÛ¥a{›ésK,CSJØ0Ά7ÛKuàØ[äÛVŽÑ&c[vfÆBK纡@Án:ò^°·|îMö_yóÇZÛ &³Ô¯×@rÆB!Î„Ž… Ü» …Á{·2në*æ…u%dk9¸Àe ǰ( d[÷qeôFƬ KÄ>õe†ëjˆ>*ù[Ÿ¶˜¼ØL^04êo%Sÿ;íèoR)ŠR³H°ïçæÜ¹l.ìL¾?U:Eœ›+Ñ“¸5³®ëhšÖªºE Z–K!„BqâÎê_ÔŠ‚aÔ¶j_·[çàA.—vÜÛ…†šHN¶¢¶òØvémñã¸ØÆÀ¸Øºe3Ï=ûŸ/ø’Ú:˜L¤'ÄÐoPg®7‘ñ&a ‹FÅÌQŸ$¹v¹œ¬)‰çš6v,{¼tkccÇúJ¼½Ãɉ†íZÝÃ}¬Íõ£¡}Ï£5EÛho°:ÏwÚBzT"Nö­ÅðóCQ[ºdµ–Ýo޶ÓÞ`eí˜ó²“yh[ÃÓt?[¸¡­Tµ–ÇùL¦².€a5i4ÀPî‚n6õÈèP, î`§v_9ß7M!)3–Ûú:èmƦiø- T›Oñ¡ø˜¢l´Qý¬8hòv¾†ËÉÒ¸;3ŒÈ­5ÔÅ…ÒÙâåÓ¼¦fãߪx´¢KZŠe£­E%ñ²L>>jÒɾ0m{5³óüüÚ :ì¨fþæjV”jGúï8±=™1Û34š?W}.Éà±.¦úq£Õòô?*š?÷æú¯…¸6ÿÎ¥Ðû’v<~t[^?Èwšü®BqºÿØ5è\˜ÝãÆ«€)àgêÚù ]ŽÙâGP(²E±ÍÜ#Terâg ‹[࿞ëx¡ö^ö©™ݹ¹!Ñ«ƒb€á¡qâ7¸¬¢Äd0”Çÿ|ž+œ†~œ¿“„8s/ƒ“[œCÓ4 iÕþþ@€ŠòrÌ‹]!„Bˆ“tVÀ&S"ºÞ|Øë5X½º†½{Ý´tm¡(™BÿþØlÍDl6'7¹­®®®áçÚÚàö…‡Ϫ©© *'%%•<TNHHhÔæàvºÝÁ·Åv8A媪ª rãöEGÏŽ®®®*§¥¥5{ÁVXXØìþgZ^^ðÔ®ôôôf÷///ÿÿìyœÕ¹÷¿UÕ{÷ì;Ìô°8 «À¸àC!\¸¹jÔäúÆ÷š{}¯7æFcLB®FnˆIÌ+q!y‘ˆBÀ("™apÖ™†¡{öÞ»ªÞ?jºgz˜] çûù4Ó§ªÎ9Ï9§º©ê_=Ï“PnnnN(&”ëêê˜Äö­Ös7—–!ájDÃ~ù‹'øåò_¢Dü¤+ù¹I´w„ÈK1±÷ÃÊK=ÂJý*ºªò׿f—;™ù%i|÷¶4n;Êv ÷1·Ã9GOÇØ?ÛX£„Žã??í¬¬G©„iígìýŽa yÅÜï¶úƒ¶h@pÚ1GU Õ¢¸L!ÒL> %?“•$GÉAsF ;u‰¤¤0Gy7xOxïç€TVãÚ/~ñ§ºDTUˆ¨fÐ;@ƒdI¼@Ô»UÐ%ÌI#¹eLÛ:vQÖzùgÛ#“ÓXXœÊèô®'kš‚¬ØÙÈóµ>Þý²›)¹ÎSêÍé oÛg»6ø¸ê¯]÷{}µ¿·}æSš¿9_nkà‘ïó^º:©yNŠ_<Àn'˯=õ¾ñÍý-ÜüÎqq²ž©ûÑaŠ¿ÉÉÉ$''úx+ È2MÍÍ"°@ @0LΪl6‰èsG‡Êo4ÓÞ>8?(]‡ƒ46F¸öÚ4\®¾Ÿ~6™Übµç-F¤85båŠ_°ùÏòoWŽ¢0ÃA†CÁ,+¤§¥£§Œä@Ò(¶ìø„Ÿ<ú=ù+,¶tt$ºäÉ3s}ô@Gf$sõt…‹ý>^mâ?âcS¸V³á9èávfíŽÚ昞ʄ<RCd`aV×EÁa•ùô¯!QšnÆB˜AeW¬L©8äXo¶·„¨US™”oFiH ­6©‰ûä†ÎÐͲ™É#L„:•?ËHjKˆ:5•1©ÐPݗǮƱÚž©mcë•n~65…‰Av«½ÏíÏÑáŒ]:c ø°yc×>c_}¬]¬ÜÞÜ›-@ œ^l¡é­^Rd?9JV%ŒdÒÀ¤‚I“†lR±™#Ø,0k$+«›¿Ê'Á `ï¼ ÀšÔù‚,ŽøG’gÿôf ‡Ä§cïceF¥IÜV°ªö‰´k®akù%™ÜY’Å«{)y¥&¾ý÷3³i uý'Þ—¸šö̧ñ÷{o»ˆªã¾>EØîbpwAw¸Ìé å­aqržØéùÛ‹ÕzÖÅ_׋Çãe|ñ¸aÕ¯Þ»wAþ€¿?€» ÷ìkëêq8ìdfd$l÷ûÔÖÕ ªÿ¾ÚЇ]œœ@ Ÿcä³Ù™Å2±Ï}š¦³eËàÅßî´·GÙ²¥¥_a«u’XmÁyþAÕø´j«~ÿß¼j4ÅIPhÓ)°IŒvXÈStœþf\¸8]âÿW/ÿñ ÑÏ‚ZKï6š¹þÒ$:upDƒ‡:8’“ÆM#"”íëÿÎı’ÝÅw¿1–ß}ÁaüÆæï`ã~•ñ—çñ­ÉN&p2gz£úúæÓ"ôèŒ(NgÁÓÆ$sUž2°~¦GxÿPgQÿ{º‹’|Ó ,¤ô8Ì‘n§$ßΔQÉ|ý†<¾šb]¥@o¶:X÷q„1—àß.MâÒ|;ÓÆ&sE¦„êàÕ=aF_–Ç¿Nu2%ßÅ—¿”ÇÍaÖWtô™ïv@$ ³Š¬´êà“nŠs¤)Ä1l\wy2Ós­ŒÉ²’c|³z¨ƒWªÂä_:‚ÿ˜‘Ì%ùv..´3²§cg4À럄W’ÃW²Bl=Fdû·®Ò„ì|%©ò°ôñØšä_’Ç—'sY¾i£“Y8Á†SœÌŸäbrŽ•‹F8˜’.CP¥]ï{n‡|Žgìg`¬§|Gõ7öÏÚWϵ; k)ÁPI t0:ÔB¶À,© h/½ëeÒm&c{4b!äëgu½Kމ¿ªºLs(ížéhZÔc Gº ¾½¿$Ì\?b?ó²?ö˜ît;¹³$‹_nkàîòÆ„}w—7²þD@,¼à´ÐÑѦ >L‹®ë§D_;¬^³–e/§¶®~Èu=^/Ë_ΦÍ[(ÛºÕkÖöÛOÙÖí§l¯­«cÙãËÕߊ_?ÍÊgW Ú¶uë7ÆËÆëĉ)@ð9ç¬zÛl%H’]²ïàÁ MMÃÏÒÙÔáÀEE§>Á(IV¬Ö’>ëîÙ³'þ~ÇŽ ûrsCGòÉÇ å’’Äv'OžœPþøãÄã§L™’P^¿~}B9‰$”GŒ™PÎÈHO(¿öÚk å””Ô„òرczÌSSâ€Ç“P3&ñøh4qMÌ=rðô Iíp$†ôÊÎÎJ(÷ yÝ3ÄuÏÖo½õVùI´ç’K翲²2¡ìó%Ê'OžL(™8¿g;öPÐÔ/<û,Sò’HÒ¸’l$9íØL&ÙŠY7cÒT$)ŠïX &¢,ýÞ·ùÖ7ÿ•y_^DFŽCJ>ƒr‰æïŸø§…²ý!Ãkµµƒ÷Ndr‘ÜÆ[ýŒ+%ŒOåýwŽòT$‹E3ò¸Þ¦s¬!‚ªƒÖ«0¥R¶µ‘Éó2ùú .LálRÖ0àÀ©ùà8ËmÙüÓey\eƒp0JcƒŸƒ~P©= eL:.R0é* <ûgû²]ãòz fñõ)9X ³w{=? fñõ‹³øÁå ²ªâi PÞ¤ukCçÐG-ìžžÃÔMü}Ð^úÀó¡GxçýffÌIáîÉTnÎ=z{ /=Áøtàñz©¨¬`Óæ-ÜóÛ‡T?3#ƒXŠ» €M›·œq{ï¿ïÞA{ðz<^Ö­ßÀ¢…óÅÉ(@p!]³ø_ôø¥zçM¥ñ`±f\ˆë:š®¡kFYÓTtMCӴο*í-ÞùÛËvØÚú;üþ·OÙ¾iS3ÇŽ…>Ó`FŒ°2o^Ú)ÛŽ/‘’òÍ>ë•——Çߟn¸§ÙS^³fMBy¨ðÎ;Êç»Üs>‡*÷OÏùw8}×Φ\Qñ×_¿`5u4$:šŽsÓ—2ÑäkÅ©¤Z-$›ŠŽEQ0)fd TY'˜WLÆÍßESà™ßý‘/ßx #Ý£®ø;ÿ—û>7_zrz:Ë—¤°çåÃü¾AGÐÅè…,Ÿèç¡U'ùXý¡Ø¹çŸFóþa~Z­¢_€s+ÎÑ —ß÷¹Ï„’Ù$¥f"Ë ’,#Ërç_I’Œ÷’ ’d”%ÙÈ.u¦,ˆ'¤Fäÿè.œû¶Ïã×_ä¼ú“ùÑkk(5`71™ÂÈö(¸ÂàŠt¾º¿€C¯½Ù‰Ç™J£+FgÇíY¼¥]ͪö[ÑÌ€E³Š];É““â›cÿ„$»@)9+Að‡¼êæ ì‹HüÇG7ñâñ/ y\ï~ÙH“Ô=4s_ÇõÌÝÛ3¿/ º;}…€nþæ„>ë4vDúͼôZž¯õ×çiõ´3ÞG[Gˆ¢±…C®çõz ‡Ãgøûíô1bĈ!¿nýFÊÊ·±há|V¯YËË~¦Í[ðx½q1¸¬|•U,Z¸€ÕkÖv†RÎçž»îÀ]ϲǗ³äÖÅTTVQ½w÷ßw/«ÿ´–ŠÊÝ8fÏ,eÑÂù,{|9‡Ç‹Çë¥dú4–ܲ8îüüÊßRQYźõ¨­«§dúT–ܺ8!Üóê5kq»óq°zÍZ;•UŒ/— ×ÖÕ³òÙ¨­«g|ñ8–ܺ˜þè'ÌžUJõ^ã>ÑÂùÌžYJm]=ëÖo ¢² wA>‹. dúÔ þÿßýì²"z\]ÿ5q¿'ç8¦³Ý¡Óùüþwé™]Óë|æ¶›šNmC’L¸\_+-81.‚üí-8|„+.-À$é˜P$ ²¢ +†ÿ¨ls`RÌ„;Z±$§rÕ•3ÙúÞ»,¾m’|ær_79圜»Œl;¹’JKPC7›SèbT0@ev2×eг+ŽlâŠÉüÍ~FMH¡à,¯Sr’ 9ùIÌT‚¬³¸¸vò…0·â=ݼþQ«˜@ œ6\~?×ï®"Ó׎lÒ‘$ÝøñRÖ{¼8µ¬IH:dé-dÑÄ$Ó>t‡ŠE²¶õ«ø¢öøey€4ž;x%)siúG E9ýTXÊüìJ6ž¸„¶Ï 8ÆÞÛ."Ûe<ôÛ]äí+ð™à—ÛNñJŽ ÆÝ9€?œ ñw8”•o£dÚTJ¦Mc峫¨Ø½wA~Üs63#ƒMonÁí6<|ý~?>°”M›·°ìñ'ùÍŠ'©Þ»¿ßo³zï>**w³äÖÅx»ŠÙ3KYùì <øÀRÊÊ·³ò¹x¢ø'"W°@ ÁyÊY€M¦\œÎðùþš°=þìá}B¡SÛp:ç£(Ùb¥ç5F:P-Æ‘L]SëëOP×f樾zqéŠ M~ÞÝó>Ϊ:núî0nÜ(jŽÔ ù±Ù]œ©ÐE9¶spæ$²rí§(ØM’¦Ñî‹°çh”¤,IâÔêš)› N“2cÎúZ*Œëd¬ ¾0Ÿ’še#õ‚˜[qŽžn„,‚ÓÉ´Cµ\zð Â/ºñ’H|ÅEàÎ÷F(h½«Š®ÁÉHo¶]‰?bݸÊGL6ÞožÅ/>úM}œ±Iu îe(Ymô€u$]gFJ S“ó^ËОžÛï 2»09a[Ì#÷¥«óÈM2‹@pAP½w À¦Í[ÈÌÌ lëv|`)™™†ð;{–á!{Ï]w°ný֭߈ßïÇïï=_öøâq”LŸ–ï7fzö¬Ò„̆{Eü¿?€Ç㇓ŽyëöELðW÷¥„c332˜=ó ʶnKè/æÝëvçS¶u›6o¡¶®¾Sô6òûýjëê_î‚|¾}ÿ÷ÈÌÈ`É­‹©Þ»ŸÚºú^Û…¾ç†h\[Wßçèö[’Þ˜ªÊïàp؄גéÓNë˜ÝFú«XX阈ívÄ·/Gff†8Q@ ÎSþ!°$™IOÿw<žï£ª†@yÍ5éÔÔ‰F‡ç l2ÉŒÝ%†*J:ééÿŽ»D½= IDAT$™Ä* ÎÿctÔ6¶h§Ù„O3³g_µ'™02›j›Ÿb¡±°˜µo¾BÕñ2rÓȻʊ• „ò'Ÿ™0Ð@ Áç…ì¦VÆ=Þ×e³¤÷‚»ïÒ!9LÈ¥ñ×–+y©íÞÒgÓlÉ»¢Q@M6^ªŠ r2ÿïÈøüN›üs&d‚è!P`X;…_-.KºN‰ó.%@›:ø>Ÿ¯õqsm;?¼z$îd KwyÄ¢ .8üþ•»Y´póæÎ‰oÿöýߣlëvæÍÃê5k)ólãž»n×?sî‚‚¸·lïmûq8Œ/GYù¶Oá²òm”LŸJffëÖoè!öNeÝú 8vfÏ,eõŸÖâñxùŸi¬±Á½1¾¸(î1oîÊÊ ¯àÙ³®'‰@ ÁyÊ?L•åTÒÓ¤©iªÚDR’ÂÅ;OKÛ†øû}d9E¬°àóäðŽ7¸Ìí" KšÆ=#²1K9œ<é!ÅžIs&,¸•«¼í,h;L(ã"ÆO˜ˆl’qºi÷Üu;+Ÿ]Åê5kq8ì,¹uñ°Çè.(À]ÏŠ_?Ýo;>ð=–=þ$e[·†ó@ œüCÝcM&7™™?¥¹ùIÂá}§¥M‹eiißC–SÅê >7Hº‚E’™ShÃIˆIN²Ž®©dä$ã …‘m“—nYBûšŸÑ9Ivn’,Q[[KöÈ1Ý[<G)““g'/ Ê«qV~mÜ~ÛHfÕç_ßó§ÚY^O¥—g±0ØÀ_„ø+àÃUq# ×Îz쟘ðÛí½®®Q4Y×iÂOub–Uôp-Ö–®ƒªƒI6<٥Ηlãå7°øèF®ðïA DOB$ æ‘ 9ã0ºŽC 1I©á¹øq iœKwyúõþ½ê¯µƒj'–?x0ô%Ô¦=óé€Ç?_ëãù>Žœœ+â/À왥qa·;±œºñ÷ÝÊ‹ÎOȧ?7Wþ ÓËÖØÿ›OöÒkßž¼±6ú²+ƃ,=¥N̶ží;v}ø¡x¹»§sÏú½Û+@ 8ù‡ÇG6<ÿ‹ŽŽ?ã󽆮‡ÕŽ$™p:çãr} I2©îÌ™3{}6øö·ÿ׎/**:«ö­\ùL¿ûKKKÏhÿ÷Þû/C:Þív'”o¹å–„rrròùùI•-Œ™yïWm#5Ån1aÒ ‡ ê&L’ 3Q‚~?‘H9Ál6$ÑÚÖÂѣǘ4íݤظiþÆV¦Ê«>u oK˜ú ÁÙ^OÉéäÊ‘:¼î' fL Á9Ff«Ÿ¤Žº,¡#¡ëÝb=ÇÅ_ã ›h Úˆx%e;åÑäf7³¯uzPÅÒyµÑuTÝj·&Ýýl’Aî€åNhèÈakx*3:>Dª×! ¢€yHá 5 §á[¦W9(åò>I,žàœC­;B¤j‘½Ÿ z —ë+(J¶XQÁç–ŒqSIJJ!î "ÉH²„Y3ƒ&s4 `ÒÍ”¿ø9YY\ŠÒæ²9xŒ¼Â²sG“ ÿ,òÿÆÑÃlÜXÇF1ÿÒÇ$11âãçuB~@pŽ]&ê:ÙžvÌÍÒ)#¡ÇD_xè`ØDC›`Ô„ÅåõŽé¼Ð6»K"d²b’ÃhRt#7pD×P5 zÔ &ÍðVdCü{ƒ1Qá-"t‘û‘0X€@o5„csn§ :Ц2V?†UŠ€ˆ¬"8W>G~?¡-¯*¯SðíÃûW×Ñý>¢{?!Zý ¡ÍC²;°Ì¼ Û5×#gd ùó+@ \è˜Î%cd9•””»HN^B0XI8ü1‘ÈaTõ$ºî@’œ(Jfó(¬ÖIX­Ó‘$›XIA¯œ·¿‰·¯è’„¤Ø "æö:"í4U¦#! éH¡6R\éŒV:(˜ö%¢. OSˆö}Õ˜]v22s•ØÇý Š¿²™Ë.ÏäÖb£\ÐÚèã•wyí„FÊØ\þçzï½ZËïi [øênnôà;¯·ÓÔiÛø+G³áJcÜ[ÿv€ŸîÓûmW—L̺*—[FYÈMR°¡ã=ÙÁº¿7²¾ACGfÌä,î½ÔŸ$™h ÂŽ­GyâÓºdå¶ÛÜ\}¤Žoo ¢H&¦]šÅSœŒv@óI¯•Ÿä•º¨á%<`½ )LžžÅí;)J’ÑÃ*ǽÞx·õRßöuöwqI&w\ìbŒ‚!Ö¿QÏ‹ÇõÏnë`ÆÒßÜ÷g_c?ëÙYïŠ";ÃÇØßU@ Î-M#µ%`8Øê2š.£ër—'°&!©º*ÑÚn'¶d`BñbŠê„Âf+fK$ 4 ISQÕ(QÅ‚f2¡+¦N¸[h€°F½'_ª {SÚkç]|´T ˜R obUEÑÀ$« 7´Í#“ÓøNin¼\Ӥ䕚^™b9%owž¯8IÕÉÏ×úNÙ·÷¶‹ÈvõµëÍý-|â òÈGÍŸiý–_’Iq¦RwRÂöW?öò^½¯W»^º:kºå9ÞVÛÎ oÔ÷;ö -ïðP„ÕàÆ?Üü:zÀ«Üí·ëß„¨nïu¿àæ¿Üü¶¹7`ûòMHÇi·S @ ø¼b:2¼zK±ÛKÅ HF 9³…+¿ý#Z>ÝJíªÇ9q¢ƒäd&]!+M&ÍåDò5sä£*ü!™ON2©p;+?áË‹ƒt¦=e&ÍÉŽ‹ðò»Çøm»Âô˲ùÖ‚,Nüá<ÉÿÝ_ÈsÒÙú'/“²¹-ÕÇSk§Iïºã?TqŒ'?‰¢¡ão×n7¢ài'Íëá‰-!Âf3—_’ÉÝó³¨ÿà *“ÓXzµïŽ<ErXpvDûpŒ(.ÉÃSáòžóBá„ îX8ë˵üñ„ôßß®ð©mŽ›‘Ï£—H¼¿£‘7¨HIIÜ57‰â$µþì“7c$O‡ò'øÃ ³SÁתŸ[#e€¹÷g__ëÙ9+.Wæ©ì¬ðr@ çÚ¸ ífIz[§,u Á’fˆÀjDA ›H‘UR«)L‰«žwÚÛi(Wt†ç¯¤kÈš†¢E‰ª%‚5£*&4Ù„Þ)ëÀ#øÛe"&R/àtæn«ÍðŽjèº ]þ˜çŒNKG§ÛX˜cgý‰¡'븳ÄðØ¼¹¶ûþ~œš :¨z×¥rM,,N呲†!÷=3ÅÂSsG2:½÷‡Äoœ”Á“2¸¹q·;¥î$ît;{‹/DÌf3’4ðýd´¶ßs¿C;Z‹Àbî–<»Ý…ÚnÇè=öéïn&¸s+I÷ý;¦ “ûí_ˆ¿@ IL@p> ¢£ ™¬¨šL•¤R4Dk‡Ù!ØîÇoq`š4›}å›HÏI£`díë1Y¬$Š¿Cƒ%Ž$'Ù\,šl¢âí:^ܯ¢ß1qÉÌÑȇU¶½×ÈÖÛòø×9 £-ìÚRË{=~O ûÂöFâý Øîã8s€]u†ï‡f¦ßœÌeYU’B2•õªOj@¨ï1X]Üx±…šGøUU Øs,Œ#£K\üùoíÄÌí«¿Ý'$lJ×<‡pñÕiŽîªå‰BDÉaf‘nüÀ&Ûú±ÏêâÆiŽì<ÂÿT„QO³­»Žõ¿¿Â;ÀÜïÛ>”Þ×3FöØ$ŠC>^ª?ÐÐåì%‚säê[–¨ËK%¤˜Q£&TMAÓ•¸0šªQ»¦cS¢Xä(’Y庴ð8¬9üš"©HºÖ)þªÈªŠ¢E0k"rÕdB“ X“etEF— WuàÐÚQP Ñ· ˆáØzðlƒ†_wàft¬Ñ6…)¹N;"qݯŒM–£ÔÄ+óÍ}z÷iOºß\W€gÝaÊ[ÃÃs7¸ûô.îi×KWç è½ûŸ³òx¾ö€ø@²,#Ëý?a*{ÿêçÐý>âGž"Èv}õ>6ë§nl Óñ³ÿÂñõ{°]÷åÓ>>×KEe•U8vJ¦OeöLÃ1¡zï>ª÷îgö¬+ÈÌÈ8§×Êï°ióÆ1¾xÜ€ÇÇÆ6oî{ŸÇ­[¿qÐmÆÚÝ´y ~‡Ãμ¹s]W @ðÙ°@p~Ün¿ñè:®‚qÈŽ4,ª—l§g–‚†Â¦ê¤Èà‹W~‘‚¢‹ˆ†4Z£*EÅ“ž7êCSV_¨ÿXrJª•Qf™œ¹cøóÜnÛ;e$Tt¿/óñ›kSÈ©=Áï(,Üî©hmaN “lƒèáV^®sqÏW )ÚÛÊÆ=­l;©Ò›‚’fcŒ)ÊöúHWä<-‡Ǣ,ee¤ÜÎ>½¿þ$J¾8š‡'*†]j;?5ÂXS”÷kÃD{é3z¼oûLi6ÆvÚ£žv[{Ÿïîûš{s?öõ‹dff‘ö/Eŧ[ pZ1 @pN]~Ë4ä$’-¨š‚ª+¨ñPÐ1XFV5œ¨˜dɤEÅîñÏ#·‘Þĺƒ³Ùßî&ª…pXÃfŸÄ݈l…#íùì:1‘“Á 4ÙŒ†Œ®(†À,IhFÚá”CÆÃu  bˆÀ Fèg`RÐ5V‹ohx)pÿÚ¾ONÇiQ˜]˜ åÖŸÿÒÁ¸P{§ÛÉ]S3â‚òètË/Édé.Ï)õ~¹­!îyfŠ…ïNψ‡avZ–]•ËU­ôº‹¿ÛjÛyl{c‚]÷_–÷¾¦(•;µõêáë «8- Ù.sŸ¶  •½ƒoåS]ú~{îHüív¬ÿÏ 9„ó_¾sÚl/+߯ê5kñûdff™‘ƒ7½¹…øÕ{÷³nýÆó°ÃagÓæ·¨¨ÜÍ£?4àñëÖo¤¶®ŽE çpÜ-\0(·¢²Š¿~€ñÅãâóyÿ}÷R2}*¯—²­ÛÏ A] à|EÀÁyá±+I`MË%oÎb>xa9­ ŒËNÆfRšÍd^< ÙæÄž3 §ÓNÙ_Þ`ñ?}ã3÷žd¦&êÿ У¼³ù(/H¼¹úÕÎÛw…1#­XÃ丘‘ÜÊk­}ÿ&0¸vOýÓuÐ%@ ñ׿f—;™ù%i|÷¶4n;Êv Ÿ®Õ‰÷§SýÁ1þóÓNYZr”dtÔ¾ôó~ìÓ΀G`ÂÜ ´ ¹OÄiÛ rŠ‹Ù9*;vNÛç;YIâ’L Î5|N;íV;ªß„ª™Puª®"°&#EPÐ&YGRt0iñ—MŽòÕ¢m”ääp ͬ—ÑÌȬ“¸’ü`?6Þ9|Oo¿™ªãÅh’‚&+è’d\“¼LÏ;ˆ°!üÆ®­bïu@ ƒ êäL¼Ñá À_Õþyí¶xþÜl—yÈaŸ¯5rìVÜ4:.¶.žœ> ˆZÞ¦üãü>¬rã$Cš’ëtêÆuåïý°ÁwJˆççk}ìk=ÊK‹Fżº2¿÷±­ý¨)ÆzñätV|Ü<è0Ö" âo¯!˜‡*þöu¬ñ>ôî[(îÑØ®_ø™m¯Þ»•Ï®Âá°óàKãâfÌ‹vÓæ·ðÇòŸG”LŸFÙÖmqï[0„î•Ï®bÞÜ9,¹uq”LŸzZûß´yKçœ~wA>¯—þè'Tì®2`÷¼Ô@ ÎWįÁyB,h³$A椙üŽýž“ì8ÞF¶Ã†Ín¦ÁÓŠ¼³‚Ü‘8PudIþÌ}ͶRãé_V[BÔ©©ŒI…†êÞ½]“Fgr_±Ê‹¯#|e·ÏIe÷ºŽé€®ŠÞÆ2Ä=JlwP"©Æ±Úž©mcë•n~65…‰Av÷eÕæ 5ÑT&囑Œ°ÊÈf&0ò„8ª Ü_{s€›»™çsLOeBž ©!Ò‡&Ú»}U-!jUÃ¥!1Äòé°u š{©ûúZO€%1ÆßÁª£"ü³@ã¢l«˜@ 8ÇðÛLœHN¢Øk&ª)†'pçË$GÑUI•@ÑAÆÈ¿+cääÈH‰QÉ ŒÊj@²–Î;pÕx¹Ln(zÖv;GŽfÑq ËŠáý«EIRê)-¬FŠê†×owáWŽÝ tºkPK»îò5àÂ{\¨­i RÞæÍCm”º Qø†1ÉÃʃû‡=^~xõHÀðæ¬ûX…7.|1ß9`½™)–ïß?W·ôz\yk˜7ö·ÄÛŸžçìõ¸µÚøÂ¨$F§ÛpZ~P’Á݃ð„¾Qkã_ýìÐ…ß»Åßóûÿ°Ó¨1æˆuë7ąʇE ç÷¹¢²ŠÚºzJ¦O×ñx½x<^Æ£zï>Ü8vjëꩨ¬Š‡•Ž ž±:î‚*vïÆãiŠ{ÄÆBR ubõª÷îÃãiJ°¡;ã‹‹(Ûº-AܵW±»*.Çlí.|Çlqä÷* ÷g[üûÓïÇápÄmËÌÈàчŠ­¶Îx@#ö·{ÿÕ{÷Q[W» ŸñÅãâó;Öá°ãñxãíw·y(!ª@ >ïX 8×Ðuâr/qéWSQ£Aü¾V~¼›1ùé¼zämmåePw¤¹a×¥â¢é£›$¢Á/G*²dêv¿¬#!uÞVëH$Ÿœ1ÚÅæOÚú7;ÐÁºÓyô’<КØ|,BÄlÆm ³ùÓ «ƒþBíu¬k ¡mñ2ëÖ þe’G>Š kzt§³àD+G0‘ðñÞñþÛèYl9ÅÉõùG<‚Š™)é2UÚ{ûM!ÔÁ«{Âüì²<þ5âa‹WbÔ„ nγî|ÃYN÷gðÐåy|+⥬ rG'1J†=Ø›Ó_>‚ÃË–†(Q«[k;Û=§ßÖ¡®©¿_ûúXÏ…YEVZyøD8Q]ß³c\bà#d³pxD:W4ÕŒ—ª™Ð:½€åÎÀ˜µÎËx5lCë°"éa$=Œ¬‡ô(è:’®×ú:x;l?2šúö4zóÑ;"Øð¡K2º$‰vpÍ¥ï3=ÿR‘è¬ÅntÐô¨.¶‡¦¢KCO)ðÅü.´ò¸q¹®ÎÇ;·Í,L޹Ýîm¤[÷`jMP¥¦)¥øó)}÷E][$þ¾¯|Á™6…;Y~­€'eðû[øBb÷Úß1Ú×s¾p{Ò§øÛ«Gp×Ûö_"Œ´90™mL½x» 4hnoåOÿ–Œ<7—_9›äädìÎT“÷ á"0½ÐÅ$Žö籩±ç½z~ÈbÉÄ,¾?C†p”šýÊ«CäLËâÚx¸"D ¹…•UÉü÷Œ .ßßÀŽJÙÖF&ÏËäë7¸0…#|°=HÙñp?í,›Vf^’ÆÝ) fM¥¡ÑÇ3o6q¨WYêmGy4šÅí—äñc´œôñÒú“¼Ü0\oU•÷ß9ÊS‘,ÍÈãz›Î±†ªn¤më×>4>,«çÑ`_Ÿ’ÃC¥j ÌÖ÷üìðDÏ€­CYÓ ~½ûz[Ïò°‹YQ¶¿ ">í“DI¡SL„@ œkÈ2Ý™ød;NÕOD3ÕMD5“¦ k*rDF2K•¢ E ª:ñû²‰š$,öŠ3Œl Ù"øØq2›×÷M䃺ñ„4²ÉŽY `ê ÿ¬¡1:³š›/{—†  Kôí.K:h*ƒETD‡ç Ù=tò{õ†WTù°ÁÇ”\'N‹ÂÒ¢d–ïoR»=C&J¶À SìwÕ™dò˜®9 º'éV™çk}ܰ¿%ž“x(ùˆ/þxè]^È÷ñ§=]÷S§Ððkì„øÛ‡w±î÷xe5Žþæ°ì÷x¼C Aœ™‘Áýßkœ/gÓæ-,Z¸ ¾ßpÿ}÷’™™Á¦7·Äæ%·,¦¶®ŽÕkÖ²òÙU¸ Ú}âçáñxYöørʶn‹×Ù´y ëÖo lëvæÍÃÊç^ÀáptŠ¢¬þÓZʶncÓæ-Ì›;'ÁNwA~ÜÛ·b÷n-\Àê5F%·.¦¢rw<ïñŸú @\ Þ´y «×¬eõŸÖrÏ7n /ÜE 0oîjëêXñë§Y½f-%Ó¦%®1ãM›9p8ìÌ›û%-œÏìYWàpØY½f-Kn]ßc9ƒ{ö¿â×OóIJnjùõ(™>µÓ3ÛÁ²ÇŸàÁ–â.(`Ýú ¡»· ˜ÓX à󎀂s ]×8¸o7ïÙCí‘ãlÛ±·Þ~–ö6@B×u¬&'[C˜’çÒp:ìŒJOE29(ÊÉDÒ5t$.»ü2^\ý2‘}5Hðœh@Ñ-dfgã¾è"Fº ÉÌÊÆˆ!×7v³ÌÜ )¼öaKÿÆkQv½œ]ï÷rs½ã_Ý‘xþ¯ü‹Ê»¶D[Úxê¥6žB»âÅÕûy1aS>ûª‰‡V5õ1Ù½ÔÕ£T¾œÊ÷|„þz©±éíz6½m”åôt–/I¡Åá†~ìë{ÅûÇ©èÍžÏlë ÆÒïÜ÷o_oë9zFn_¿kែ×LLÁj’ÄDÁ9È¡QÉL#õd;ÕLD5c–ͨzE“‘£2DˆhHa“9Šbi¥1(ózÍx"Q+ÉŽ0ª¢áQejvö¶äRÓ°(6L²Œ®EÐ%]–Ñ—«;foæŠÑ‡»<ƒôÈýÛùWÖ ©f^‹|¯ž1¬ðÏ1/X_XMõ¼ë˜Ÿ)¹ÆJ׌º<Ú–(®n¼÷¬«›0{´Ýxlð¥«óâblŒÆŽÅ/èµïÁˆÀ¾pÿÇüO¥7Þç”\'K‹’ÙÑ  =à7ûÞ ÍabmQ‹{ž}xïJ'ÖK¯ÀþŸ/îjjš‚ƒÎŸûƒ’Ä£^8`¹ÛŽúâÞÀ=)o ÓØ‰{3u|j¯ðÌK˜c9ûcÅÇÍÜ0.•l—™l—™»'¥Šð—:#ŽN»YbÅÔ4¾¿³Ç½b§ð+9œ¤þ×O1Žæd;¬}[â“c';—Èi…KGé|¡Xgâˆù¨öIØŸB NlèÔO(fÿûÀ}ÃG,ßnYù¶S¯X 8±Z­¼úÊ+̸â êë5Iê|ì_‡gWÿËí7’5~ ¡µ4iIa»¶b›:¿¾òoÿ};5‡kÉÉÉ&QYU…ŽŽ®A~~ÿ÷wÏ`±Xm×âËÒÙߤü@Ç)û~õÖ ±p‚Y!¦@ ˆ3»(‰›.M!ç8‡Æd²cb>yï·`‰1Ë̲!ËšŠ$iH)¤w À:È:²¤3#ó(²ø¨9‡¼¹Ô¶¤ã 8ˆh Š¢’–ìÇ=¢‘‹‹j™ZTO²3 1Ï_ Cøiž1àh·÷{“8™”Ž¡{ÿÞévö™·7æŒN”¼´(™¾8#Á‹wýÞ–ë-̱sï´ JÝ]H¾¹¿%îu‡·ÃØVV¾ ·;ŸÙ3K©Þ»¿~š’éÓâ!©«÷ⱜƇ’iÓâ}m:X·~CÜب÷‡=.ݼÇy“7¿Eff:³g–RVnó™™¢ðÆ„±¹ ⹄cá³có±há‘X Á€‚s”¼¼<6nØÀ P__$Ånq% ò«çÖò·Â|æ–^B~z ¾“í´†#”ÿù¨«Çç¡E5d“‰êê‚Á0Hùù#yíµädç É& ø÷kóø?mu¹¯`،˵ñ½y¹ˆÌ¿@pîµY)»l4ï?ÎÄÖf%ŒYµb’¢FhIEŽêÖ‘:Å_$ݸl— ÅeVn-3܇ *2>d"²„ÉÅé cw†1™ã aˆ¿2ÆÅ,ï/$ÀºúÁ|Þd»³Ž†È c’ãïkš‚”¼RsÊ1LN‹ ŸSr}†ÞxóØ>ûÙVÛÞ§püÒÜxû=iìˆðÛ=žîžº`„­Þèî;ÞΫ{r÷Ç#5³°85AÔ¾ÙéMÌ»¬w†}^15_½“ø`°ã¦Û0O˜Ü%þö‡{úönÜ#¡#sç¬Q„Òcó<׋§æ6?–ìpØyôá‡Xùì*Ö­ß÷ŠóˆåÄ ±»e[·Q¶u[|û=wÝÏ[;T[—ܺ˜ÕkÖ²ìñåñíî‚ü>½”Ç£¢²Š’éÓ¶—L3B+w÷¶½ÿ¾{Yöø“¬|v+Ÿ]ï³»°ì.Ègõšµ¬^³6a<=Ccßß½¬øõÓ ÇÆìÙÚ+*«xð¥}öËùÛÛ|Äúé9½‰á@ BÎa¦L™ÂÎ;¸ñk7±mÛ6$©ûÓÒš{Ô±ÿpFø-#J´„,I ëȲĤ‰Ù´ùMLŠÂ¥¥¼ô§5ää /LœÕ$ñó¯åó‹M ½z  f%±ôš¬&!ÿ Áù¡±Y¼5£ˆ‘›Z1GØå(ŠEV£È’†,é(‘Έ=R—Ü•¾T¤GqYU\Ö0XU°¨ i†‡¯(t‰¿=ÿ‹ˆ‰¿*è¨'\èmV¶^D¥}ü°Æ4³°Kýûáö^yá@÷\’÷š½RKwyÝÇ›û[¸ùãC¶íþÛ0¤œÃ5A•o¼VËSsG(Ôn«mçîòÆ!Ù´bg#˯u‹ð¾ç@\ôíNe–×F¹¸ápG|¿ãú…øBðÂÖÅßX“¯í‘¸l´Ä¤ °´lDŽ6öQ«ë½><ìñdfdðàK©­«§zï>üþ™™é†Wjg(áÙ³®`|qQB~ÞîÛ /ÕÄý‡XJEeµuõq/Üžmv§{øå>°”ÌÌÎ:3K㢮ßÀ]Ÿú”ëΙ¥qßî,Z¸À°¥ÛvwA>O,û »wãñ4‘™™ÞéulO°Ãï$x÷nÙhë±øØ{¶ðèÃÅ=333ÈÌÈè·ÿÞæf|ñ¸Sê 7d·@ Áç ! ç8¹¹¹¼ýÖ~ú³Ÿ²üþŸÏ×MÖˆý¸$!I†ÃAìVX6+˜M&À¤(|oéR~ðƒÿÂjµ~&›lf™ïÏÁ+4±æý&M,”@  €Ý,sÛŒ n¼$Mxþ ÁywçlæÝÒ1œhb~姘äЬ¢H*²Ú•ªE ë†3n÷/zÐ$ã¥J Ê•!"ƒE³& Lº!þ*$ŠÇ1¢@T‚¨Œ¤ëœaaMúuxåÌ!giQrB(äµz¯\T)?ÒÎ5ys¿0* €;"¼¶¯…·ë}¬?’]ÏWœ¤¶-<`¸ç¾(o SòJ LNãò‘΄pÒ`xý¾Wï´ço‚mµ>nØßŸ‹ ™c¦S¶Å¤Øg'¦pe½WDÇzéH'¯í”ð…N=6^îE~§ZbÒ¨ór,­ú7H×QB5Ÿy\î‚ü^sé‚!÷:»os8ì}æ-™>µW‘¶·6ûËåÛ½Þ`=\c!—‡²½?ob2èsž;ö¾ÆÖ_ÿ}õÙ_@ ‚ ú6VL@pîcµZùÑ#?âÞ{ïå§?ý)ÿ藍£]–Ñu]’Ñ%E7n—u]C’t$ --•Ñc.¢jù¯5jÔi³I¾vi:_š˜Ì‹;šxó“VÂQ],–@ ôÀb’¸fb KfdêPÄ„ÁyŠ/-‰ WO$£ÝÇìC‡PdÕÿ,iÈj×õ·î®ÝJ†wo‚¬BnD†ˆjÀæNXÑ:s“èE• ¤±&Ñ*Ùù­óV6Z¿4¬±,ßß6h‘õæwŽC/ÞòÖ0iÏ|:¬¾‹_ŒåµÛ£»]9&RRRرcEEEØ0FÈâ]uùgh%KKi[Ç`Áš]™Ûø©ã@ÓT‡ÒRßιØeö¾K+ýÓv§R\\LLL µk×Ö‘ß¼c.6ÂLqo¤ÀR9K\yl7Ëè©‹Ï©ÙC+*Ø5]PT5íóù£¦²¨éZµR÷Û¾$jºnFôÇö­[¢¦cãã¢ëSÝ©‰Ñûß–]~r|tÚ¯¢²Htý°}jRôò­Û ¢¦ãã}ÑÇ«8º}ÒëÔÀ«± EDDDDä7Ä0 JSù¸'–6HcÈó°b aÛ1qÛeî »l»¼pØÄˆ þ–€—CØpÈ ¹ÙYê§4Ë´:y³ùé,Oo¢—c.55•ÔÔTÆÕ¿ÿ ¶»¬\vÊ/Üi{€{j´j‚‘ˆˆˆˆÈÇ4œg™ìÙ@ë°•¹Ãð•{}"™$91:¿2–eñ·¿=Å€ý騱ãžù?ÿü3'NâÚk¯Á4ÍCÞǤI“˜3gÁ`ˆ&Mr6l~y°|áÂ…|óÍ7“““ͰaÈ‹‹ÓÁù-òxYÜ<›•õ2˜º,‡¾?ÿL· +iX¶¿S†eºð¸Â¸.ÛÄ´ìò1€w}zÿÚ¸m“@B2_78‰÷›œÂŠÔÆêõ+""""""嘀‹ðaþîVfD˜ìÞÈ©‘øWµëúüÑ»Ù?Ήš>©GϨé…óæFMo‰ß¯‡í¶QÓ3Ó£¦×n‰îA|r÷öÑåÿ8;j:95º~[·D—ß¡{tý¾›<-jºYƒZÑõÙoû¬¬Œ¨é€?º¾íÒ£{8ÏÝ>™YGýÜq‡/¾ø‚´´4êׯØË/))¡¸¸˜ .¸Ã0øßÿ>`Ê”)œrÊ)ìØ±ƒqã>eøðßÑ A¾þúk¾øâ Î>ûl]MDDDDD~£ Ó$”˜À´­˜Õ<‡F›7ÓyÍJÚoZCnÞ&êòItÊð9¼v·maØ6v,·‹Çdg¬Ÿ%)u™‘Ñ„IYX˜–ƒåõ+ø+""""""혀-¦¸7‘àïneF„)îô gaê–ùWÅ4 Z·nÍG}ÄÈ‘#÷ôÌÝ×öíÛùôÓOY¿~=))µ8í´äääðþûï“””Ä©§ž Àüùó™1c#GŽÄ0ÊÏ“ÄÄDÎ<óÌ=eµh‘ËŽåóM›6‘œœDÓ¦M1 ƒnݺ1vìXB¡^¯WGDDDDä7Ì0]DâãX–“ͲF øo HjQ…;©S”OFi‰¡RÜNÛ„ ÇËÎØx6ŧ°9¡›kSêÇq{Ô˜""""""ò‹“ð׎ù[°i;«g, `ã6’2ÓhÜ­ ‰uR¸í#Àb×NZY©:Ò¿2í۷Dz,¾øâ †µ,‰ðî»ÿ!''›ádz|ùrþûßÿrõÕWÓ¸qcæÌ™ƒã8†ÁêÕ«iԨўàïþlÛfÕªÕ´iÓ€””òóóÙ°a™™™ƒA"‹ÒÒR€EDDDD(·‡p¼‡Íññlª“ Ž å¿÷¬h‚Qþ»ª{‘ƒuÔÀEFˆ¥®ü­»uÙZ~>ÇÞ{ƒ¼cõFv®ÝLë!=Ikràô¿K\ùäØÉL-'Ã04h/½ô‹-Š÷wݺue 0ÇCÇŽY¸p!‹/¦eË–|õÕ×ÇêÕ«9ãŒ3ªÜÏW_}…Ûí¦S§NÔ«WÞ½{óÎ;ï HLL¶mÜn·ŠˆˆˆˆÈq*ÎmPqŽéý Æî{RÝ›JE±n}@DDDDDŸ£µúÙ•‡Ão¼ÃÁK¾ù!*ø»›cÛüüõ Rê×ÁS}j¬6 ];èI¯¼œ`ôMWí蠲ߨ~¹Ø½~LôMÛº-¨éfu£ë»fͦ¨éZõGM[Å›«-?1£¦Ób"QÓ[ƒvµÛ'eæFMÏ5]’™\íã÷˜Çîäõûý 2„qãÆÑ»w。¸˜øøø¨ lrr2EEE$%%‘‘‘ÎòåËIOOÇq²²*Çx„ ¬ZµšK.¹8ª¬=zУGfϞͤIßVš†ZDDDDDŽ­“]ÌØQCÈq«M²¾ """""‡ÏQ ß…±YkÕhÝ«6T&:±cõÆ•µÆ,"‚­£ý+Ô¤I5jÄ÷ßOÙ3/>>žââb"‘½ðäçç“€a”¼hÑ"-ZDnn..WÅíùóç³páB.ºhqqq•î;‰0sæ,š7oVi"""""r|¸<ǧF£"""""ò›qT{o2K°¨YÚ­`qé×)*©QYl6™%Ô·tÄ… ÀóÏ¿€×[Þ»º~ýúø|>&L˜@÷îÝY¾|97nbèС´hÑ‚‰'²yó.ºhD…ò,ËbâĉœvÚiøý~"‘†aì ò†B!¶lÙ¤I“ƒôíÛWADDDDä8Ö/ÃÃuÍ|<¿4 ÆãÎuÍ|ôËðµýíÌ+P£‹ˆˆˆˆüÊÕð³´ÆëÆÄÇx„¸ƒØw™À¿R ôéÓ›Ù³*?©ÝnÎ;ï<>ýô3ž~újÕJáì³Ï"%%(ï!ܤI ÈÈȨP^~~>…¼óÎ;{æy½^n»í6 Ãàå—LJiÞ¼guV•=„Eäðøúë¯?~<ŽãàõzIOO§]»vôìÙçÈ}P¶dÉÞ|óMî¿ÿ~‘_Q¹~Ú&»xueyye–£F‘cÆï2h›ââòœ£üñº(..Ò9ˆçŒˆˆˆÈ‰è¨€ ŒP×­Õ8Ï[eh?†ÔF™±ï`¥óm3&jºuóFû-·«]îrêEMoÏ/ŽšNOMŠšÞ¶3úF+ÞýF² 0:HžÒ°}ôòýz='ú¢ëÓµwôöñ¾jë—´ßöÞvÝ£¦b|Õ>þ£ÍårqË-·T˜ßµkWºvíºg:--Ë/¿¬ÊrÎ:ë¬*—¥¦¦r÷Ý©rùï®"GQ  ''‡K/½”P(ĺuë?~< ,àꫯ&&&æð¾Vðå—_²páB‚Á €ˆˆÈ¯Hÿ:^ú×ñª!ä7+ÆëV#ˆˆˆˆˆüÕwþÅF¸Æëzb¼äžÒ•ŸMÁ±£ƒ”¦ËEî€.¸cü~?III4lاžzŠ)S¦Ð¿,Ë⫯¾bÖ¬Y”••ѺukÎ:ë,bbb°m› &0mÚ4JKKiذ!×\s ¦iVº¯ÒÒR¸îºëxæ™gÔø""""""""""rB9ªàöA­ŸÖ¤>Î;•U3P°qI™idwkMBFêA•®bß.OtÏ1ÿÊ©¸<:Y§úÔÕu3ªï©–˜Ríò¥Ž?@ýT¿¸„ƒ}ü""G_BB­ZµbñâÅôë׉'2gÎ.½ôR¼^/o¼ñß|ó C† aòäÉLŸ> .¸€ôôtŠ‹‹1 £êëtݺԭ[—¢"¥Æ‘ÏqŸû'±N*íÎì«#%""ѯ‰‰¬\¹˲øñÇéÛ·/ 4 gÏžLŸ>òÃ?0`Àš6m @RR’ODDDDDDDDDD~µŽj؃I«Fë†Jä­ÛB°¸Çq¢–†AL|,)õ3ðÆúj¼o‘#Íqœ=׬ý¯]òËØ¶mÛX–ÕswçÎø|>"‘………|ðÁ|øá‡{Ú>99™P(DAA)))X–uPûµ, Çqz;©ÜîcgFµ=°EDDDDDDDDDäÐÕpœã!hTÿAz8dù·³ÙüóêO àNn#šôíˆÇW}jåxÇ££}PBù[)ŽÉ Öó>G(Þ²p­:¤«fŽ”bä-„àpùp’r!¶Ž£½gŒãD*mÛVø0 …B„ÃaÀžÀaaa!óæÍ£G„B!¼^/ƒ¦C‡Ñ—†H¯×˦M›¨_¿þAí7‰D:‡a˜¦‰ËåÂ4MLÓT XDDDDDDDDDä8ªàd'†FÕ¤ Køé¿(+¨Ù¸‹Žã°iñ*ò7n§ÃÙýñ%ÆU»ï㕽áM.úÝÞþÿËuUþXÃ!"n/ž£õYyx ÷ô¿ ß ßrßIžêëR:Ž?öü7]'|À5 ŽrOëÀvÜËžÄÇd<)nLŸ'bY[F°¬ ¡ú7òÙÏÝèÝÑ">VOx92v÷ ‡ÃlÞ¼‰×S\\ŒmÛjœÃ .ÎGvvC¾ûnRÔü^½ºS¦L¦K—Nå3yòÄ ÛwéÒ‰@ ¤ÒeÒ¿ß_´Tdš&ññ dffQ§N<.—KA`‘Ã쨀Óm?+Í‚J—9¶Í¼O&×8ø»¯²‚"æûŽÎžVåÉév5Ñ?§ŸÆþ…»ŸϼÍü¹ô¾öQž½ºG#ll¦´ãŒ‹Â4®SyðÔÞô#Nþ’á3^ãüäcûAù‘®KxúÝôÅÛ\K½™÷Ð÷Á:¼ñáõdW7ŠVá[z ñ¹ wJô‰‹< WÞÉ×S¿%68³_DÏx9ìvƒÁ +V,cÛ¶­deÕ§qã\.—Hd˲(**dÕª’“Ó„˜˜EDDDDDDDDD³£δãpa`Q1-êæÅ«)Þ–÷‹Ë.Úº“Í‹VQ·Uv%Ò¤n5àðOrÝË8ù±7ø{çTBæ±Ä¬ÃQËfÛšóoi]õr'DÈ:NRÉѺØløiNë!Ô3m6ÏÕfõ+‹¡Ù¼KÿDB+/3ç[Lœâ¡^êgš•8Œ}/HN±iCÀÄ´y†ö-Â¥¡ åp>v¥}‡ÃlÙ²™­[·Ò¢EKÜn·Gd?.—‹ääâãX¼xñññÔ­›¹'5´‚À""""""""""‡‡ûèî̤À*³°Â²-KVrù[–¬®4ÜÀNÀMU‘?‡âU+ؚїóÎèLа1-w/ÏæÙËocì¬ål)õÑ鎷9ÿ©ó*ßÞÛ6ëÿu6ý'œË”7G¶ûóëÐtž8ÿvÞ\´–í¥&µÛþŽ‹ú†™üßñÌÛâ¢Aÿóô5tˆ7 ôCF¶Ý•B9} oÞv[ij6]o~ƒ·Î‚ßðÇÜLþˆ‡Î÷~ÁµóÎeteuøw¯¸7¬a[©‡´–¸òž¸¡g&Púó»ÜwÇÓŒ›»O“!ÜüÔ£\Ú*p(œóoî¼ý>[\Hb³|…ƒ*k²¨ºLæãË€Èbž;·n*Ÿݗëý;7tM¨vŸ{Á¶7/¡ÇßOâ–É IDAT°ÂØæä¼ŽÆ6/f@ÜûL¸«cÔÉjlO\½|,;Žï/¥uS}ºŒ=ÇKï™9/Â[‡ô»lÜn¥7…Å&)‰JÉ+‡—mÛƒA6mÚ@ýúõq»Ý”••©aDªà÷ûÉʪϦM¨U+·ÛiêÛ9""""""""""‡ËQÿĵ…U “н|Š¡÷ïnÅÛó+y€-¬”j¶2HìÒŸN;ÞäÏ~…)kK£û'‡×2{: ÿ÷TæÎþŠgÏoM¯~'±ãûïYa3kúrûô$u߇ÙÌâù1œÿÚ4fÿð!Lÿš1ŸÇqÝ+ß0mü#œ´äqzû‡#‹Æ?Åýs;ñ·É X4í}9³qyà3¦OÌ]Åš5Ëùàšô¬ªÖ&ÍñröØÉÌœö O)äå+ÿ××ÚP6GGÞÇ¢.óÅô¯x¬ëbüÓK,±€’ÉÇüÕïsŸcvÑ,[õ ·¶kÌUÿ]ÎÚU¹£c6Wýw “÷ þ¸ ¦àN‰Ãå‚Oÿ•À‹ÇñøåAå«/ˆáµ'ãùò•®ô%q?Ð+í’”Z¯}Çþ-..&11I"R‰‰‰”””‡±, ÇqÔ(""""""""""‡ÉQÇ;šZÉæG¡C.;\¬0¯™•LœS}2gWöüûÃÑô/|›ß÷íD¿«þÎÄá½+q¤gÕ!5­>Y©^RœN·u_òÕ* Bs™6'“Þ½ëUÒ˜~jÕM'­nÎ?»±ž:4iVºÍqNßV/]µ]ü~¼kX²)ˆ/-›&™þ=‡ÊCLŒ˨¾F,éõ³¨›•Kÿÿ{Œ?´˜É{ãÖœõæ¦Q}ižÍÀ/§ÃŠï˜¶Õ&8aËdFÎ4º¶âÀ£>göZÆ™£žåÿø‰‰‰dee‘@ii)7ndÇŽüû?á@-pÅê/GÄ/ ^…Ëð®~ïö xbKp%ù0c<`8%¬-A"6ÁØND^@$ãd0ÔÃXôÜ‘ê¹ÕŽ}Ž›>‘L&»7RfDð'ÅÓ𤖇¥l¿ã¦o¤>§f=>Ëf¿Çëk2éÑ®!ÉÎVf½þ«ÛRu°Åßãr.ôÿŽ¿¾’Ê¥owÆ{˜ÚÅÚ¸€9ºäÖM$»eCâÆSš”E=ßb&|¶ˆSzÙÌ UãĪë`oà‡¯¦Ñï´”N}‚~ÊeÄC ñežÉ`ó:}¬^Þ‰ #Ÿ Å)´mVÏIg3<ù"FßÓ‹´Q'S/²‰¼Ôݯ~FJ%uɨúñxNªzŸ{[× í¢×˜TzÃWÜÄÔGz²mìùœ±äÿ˜öh*‹;õ†¶üÆÜÇŸž cÊ8É ÑØ²ÉñQY).î¸ÐM—Ì¡`zÆË1gDŠð.¿èsüMkãi“ŽáªúZÉß@`ùí”-oL Í_±“Z¨EDDDDDDDDDD¤JÇ´;Y’é‘ÒAŠÚŽS# Htj’µ)Ì_ÉÔnæüSzЭÿFÏkÆ­/ÞE¿ê:ŒºsqI\ Ï⼎žÃT{‡?ŽeÔÐÎ4ËiÍ '·2øžkèß›ßßÞUžN—žçpLJ+ËÇ®ªNˆåïÝÆ°=9çñuô}â®nꂸÞÜ=ö/4õWÎéÑ‘Ž}Îãö÷—–wŽ9‰Û_yŒ^ëþΈ^éxòÝÌLkK“Zû"1UÔ¥*Õí3JsWÑ¢ckÜ™7{ÍÛ·ªò NJ[JÞtI ðÁðú©ù\Yw5'ûV0¢öj^í½ƒÿüÎo¡dM«Ùõz¶Ë1g”®'öûsII™FRŸ¼™ÉÕÜɱÄwnDJëbâg_‚{ãgjH©’qê¹×îÊ¿èìúç€ãà8à8åcóá8ØŽ³k¬>Û¶pl»|ì>ÛÆ¶-Šò·3éó÷Q%l¹v²Ô•O„_6–¦‰A3+™ÖVêA§}>8aJ ƒØÅsxîú[YzéG¼tV:G·oi5u(ý‘mÿM× pMƒ_wºXÃaθ _Þt\…åŽ!hÄîöNbÓc^ßÙ³g1xðP]u~E,Ë"——ÇüùsèØ±3eee¯QÁmø§^Hr;îÚåã•Gv”`ˆiJM."v0Bá´Õ”4’HF9!ùýþ=×Ä6mÚ“’’‚ÏçÃåÒ8í¿6-:ö"!¹6¦éÂ0MLÓÜõÛ…aåfù0†a˜åÉ: c×u±üâh(‹‡È>ióÝ}›ˆˆˆˆÀɃÏÑýžˆÈqÎ<>*aÐÚJeH¸!Mì$ÜQ-7&ÙvƒÃ ikÕ>ÂÁ_ ²˜ç†·£eß[˜Õõ~´ƒ¿ÇIŽŽéÅêþ,¥í¡4®%¡ˆM¸,ŸP°”€;â—êÿñqü•ß6DZñþt‰¹îÚñ8›¢i+(˜ü3Áµ;¡†ã š1n»6Ä¿ðNŒ²ÍÇ÷ƒ¶7óÅCW1êµeÕg ‘ÃÊ}>+eRßN ¾ #$5g˜å?"ÇÛ5mÅ+ø[e”OØá­…$tÍÁ“þË®qÞz)ø–~A8x3vLí£ôüJ #;›ì:‰èë"""""""""""Ç/EËDDŽ #”GLh.®ä]ãT›)CÚàIOàËÉaþx)?ÌTºí·g_rѨb>þ:´O¡“¹iÂÑ{ ®¦\òÏ÷xqdK<:¬"""""""""""Ç-€EDŽäE6žoôLà ¿Ðá–Ñ¥|þm˜??V†µß@¹_}fÈE¼ôNÖÍ]tm°Á“–€;ïǃª‹“÷%·õéÀ)B A¾»³Í=Å¢ÝûMæÏ'µ`Äë›)Oºï÷ÞHZ¶¹qKxúô xl^ù˜¾öf>ÿë% =¹+ms›Ò4·=½Îúÿþ©ÊF4vŠfñømè2ò-V†÷¯‹ˆˆˆˆˆˆˆˆˆˆˆ. ‹ˆÉ‹lÉZ\ GÌ Báò¿ ‹l{ï²y‹-F=PÊ·°å°fƒMd¿±+>£díAÖÆ!‰± †vÝ;³f.óòÊC¶ÖŠÙÌɳø§1ÿÇù8ízÐ9vÿ¢ XöÃL¶7¿Š¿¿ü¯üãÏ t}ËÃ×fò~]'´‚·Gý×Í+øçß/$Û³]DDDDDDDDDDDäpq« DDŽ +€SqÔÜŒÚ&ç òòéÄ0WžãųO^åä$ƒQWøÈL7ÙºÓæ³‰aþút/>·ÏÕÛİKª*FÊ þ6uОi§KOÚñ$3f—qÁ@[gÎd}\"Æì,ŒœBgc ÓgÑô¬n¤™•¥©6ˆËéJßžmqÑnëùþ외 BßλV‰lä³;åáµóÖ(:'•ÖEDDDDDDDDDDD€EDŽÀoöó×Q~ÜûŇdš\}AÌžéËΊ¡¤l¿ž²‡¡ã¬‘Ö›~­äå)s Ú‚S—ÐþªkñÿëS¦¯´èàÎô 9ùäF¸X~Àò̬Fd™ùäåïîÎl³éÝÛ¹µ,“kþ{Ò”tBDDDDDDDDDDäHÓ§ñ""GRL-ì@l§Âá8¸M§Òå»LüDÍsaoê!¾Ô£o¿fìœ<‰…3ùvv#ú 9ƒ“[¯fò÷Ø4ù[–döc@3WŠ3Ün\ØX{ÒY¤tÂÉuWóêÝO3³PéžEDDDDDDDDDDŽ4€EDŽ ;¡ ‘¼Rœ°Sáç£ñ!ƾdîÂy;mÊŠ%Å…6k×Y|1)Ìc/(*°+lÙQŠßäkç¢ñiƒh¶ék>û9ÓS{Ò«a½OnÎâñïòΗs©;p­~q®_ó óÆ#ôÝþ"×ßô«#:'DDDDDDDDDDDŽ$¥€9‚¬„f„м8¥0¨e³æFxw|yDÔeB¬ßÀ0 r„Ê׉õÁeƒ]Äí·mhs‘=ª.NÞ—Ü~柙=à|toOâW£Á k5†ÇŸßLãkÞ¥¹ËÄ8õ4š?ù8/Ú¹öÎÖ‡üBá©&>»š=ÄMÏ·áí?´&¦’ºˆˆˆˆˆˆˆˆˆˆˆÈ¡S`‘#ÉC8m ÁõyØÅVÔÏ­ç˜ ënb`ÙPTâPX¼7ø›™jðÏQRÝvÔvVAˆ@+­ÇAWÇqœèáƒ] vNW_-*R…Ù³g1xðP5įˆeYòòò˜?;v ¬¬,úB[¼–„i¿#.+c¿ž¼°h×?9,Ýä CF2tkfrjGƒXoÅý¶n£8ëÿ5¾TAN8~¿Ï5±M›ö¤¤¤àóùp¹\jœ_™{‘\Óta˜&¦iîúíÂ0Œò¿  £|Ú01 ʧ1À€]ÿa†T~óÇA÷m""""r<8yð9ºß9Î)´ˆÈæÄ7 ¬á5¸Ö¼@LBZ…å-¡EßýçZPvIôÜH¨”€Ñ˜p£jX©@`‘£ ÜôJ‹WÀ¶¯ñÆÕþEeX¡ÊH ¬Ç?p ]¾EDDDDDDDDDD¤"EDDŽÃ$ÔáXø(öÚ·ñÆÕÂtyk´©ãØ„KwˆmF óÓ8¾tµ§ÈfÙ¥!›’ MØrˆØ–ýëµÚ¡<é–ãÀ–Â0– íêûIŽÕ[D‘•>Ý9JÃE°õD2NÆ»ð1bŠWáöÆazü†¹ßʶ"*&äø7¹‘pö%êù+r„…-‡ÅŠƒ6åáÑ_·Ý#-ÔIò±Þœ¾“¼Ò—ö¨MƒZ^"""""""""'EDDŽ2+­e}ÿKhçO¸7~kçL\e1¬RÀÁ1c°½)XI°êžB¤ÎW¬NärØQ¡ ÔÂù ~«|chœÖ:‘µ;CÜùßõtÍŽãº~éxLC'‰ˆˆˆˆˆˆˆˆÈ B`‘cÁpa¥vÆJí\>iÁ Žá—öï,"G„e;l.S¶Õ»4¨ååš¾iücÂVn rϰLRâô¶QDDDDDDDDäD è‚ˆÈqÀ1cp<‰8ž$pÇ*ø+r”X¶Ãú<+“™ìáªÞi,ßàOÿYÇÎ’ˆEDä·ð¾4TÀÆU«ÙT[ˆˆˆˆˆˆœ¨Ô•CDDD~“6„ [ þV%;-†>͘´¤ˆÇmäѳëãq+´ˆcöÞu¯­„æ—?Øsêa:¥,|ó^îzw)e1Ùœsï\ÕªqÞËkswP²Áôà‹O!³Qs:÷Ìð-©åÞ¿L«âþ<¸åµ»˜PùõÏ)YÃä>àÓ)óX¾)Ÿ2ü¤f5¡}3¸äÜNd¸N ×Æâ‰ÜwÙ¦YM¸òqA–¾”("""""r"RXDä0óûýj‘ÀŽ’ˆzþÖÀém“™½¶”%›¼8y7ôOW£ˆÈq&ÄšOç¯ï.¥ÌU‡£îbd›x {ÛwP´1½~â¼¢­¬˜»…ó¾çó pÿ½çÑ2vßÀ®ËëÃçÙgžÇGUß}qò~à™»žàÓµÁ}F/fëʹLp·å‚ó:XMéØX¶£SJDDDDDä§°ˆˆˆüæ„-‡‚RK Q^·AÏ&ñ|>¿€/pf‡d²R¼j9NXlžô ù×O)œtõ]ŒêU{¿±ŽLšŽx‚1çÔƒÀV|ýO½ü=½Ë“ï¶ãù+rñì³n“O–÷*>Ю¾{éY>]„Øl†ŒÉðÎ Iu•±uõB–Ó–z» ±ó˜ûÑë¼öÙ,ÝÆŸÑœ^g^̃š‘`ÖjÆËçK6°-¿ˆâ A|fô~9× Ì!¶¦ëX;ùéƒWyõó™,ßi_·½ÏºŒ+4Æ¿{§˜%ãßâµ§²`C!v\-†ý÷Ÿ¾kydc¯ÎXÀÝúj^=”t%€9a(Ÿ“ˆˆˆüæì(Žà N5Õ#'—i`Ù/·M ""ÇÂÙ/ó—§¿c‹•@»Ëîæ®ÓPÝWTL_:m‡ÞÈMƒ30‹ “¿cñ/âÜÉ›ÎgÓ p /m.þ3<­5 SˆON'»}?¶OÅpJ™;önîû ó7—bz Š7ÌeÜ?ïæ®ÿ¬$ `ocÑŒy,Û¸ƒRbIôÛl˜ÇgÏŽæ_så;¬É:”1ï•{ùË«“Xœç¢Vm?eëçðј¿ò·ïòv½òøùÍ{¹õŸ2sm¶/ž˜àvŠø½pÃKRF=²²ê‘•‡K§šˆˆˆˆˆÈ E`‘cɱ0ŠV`¬ÿcù›Ë_ÇXó!ÆŽŸ0¬€ÚGä°l‡âàzÿ:E¬Ÿù9S—¾0ñ‘(ó(Iô»h\»<¤òãšR ê=-"ÇÅMóæ°>dP{ÀÜsV5ˆÃKóÖM‰1ÀÎßĦR'ªÌ%¯\Ï ¡g2pè™ zW¾ºŒÊ®zÖÆµlˆ8àªOÇöiTÕAÖÙ1‰·>[OبÍÉ·¼Àß}¯íH–|ø?¦—ì³²‘Á°û^æ7žå†¶> {;Ó§/%*F]Í:Îöoyë³uD<¹\ñ÷—yí¥±ü놎ĒÏÔ/¦±Ãgçd^ûp’èzÝÓ¼÷ök¼ÿîk<ñ»ú{? p5äì¿þƒ±Ïÿ“oîGªzÿŠˆˆˆˆˆœP”ZDäX(X‚±ìU\Çc†ó0Ý^ Ó`[ØvË6°ÓºC“‹±ëöCßÙ9JC5÷7ò#/]u!‹¯›M·æ‰–ϽD™GQ“tË·±l‡ï—1¤m²N&9Æ <^7‘p˜“ÇòÏöõùÓÉujv“ë8ûU¦;.‰dßî>¯&µb«(Ѩٕ<²r)ËÂFRGN뙆ÈêׇÖcbZÉ ~^oÑ;{¿\©ä6«9w=…y…X•ݼW²Ž³r KCŽó3c¯?—±û¬nnÛÌV ’–/æç ƒ‘Ô…3ÖÇg.?±.pB:«DDDDDD~ 9šB…˜sîǽî#<¾x\¾Xð×­ru»d!á×IhÝy4NrKµ¡È!* Új„_ qí˜=Ï\]¢°ˆL²Ïú׎åŸÓ6ò͘ˆ‰{ˆOJ>À—lJ™ûãb‚˜©õÈòQeæœûpÆveÔ¥Ži°ÅZÏœyÛ¹°AúaýrÛ³+!³mc×pÓ)ÿmxÓçÌÎ{Ç Œ„–¤¹gwyæþ6Œ]ÁŽŠ‘‹ˆˆˆˆˆÈ‰vÇ,""GGñZ\_÷åK|‰¸¼q8U|Lèìú1Ý1ÄħãoÀ=álÌuŸ¨EQتé'Úa~º¯Í3ü4ÍHá•îš½–I_ĹݲhÛ°.ýθ–·æ”§uvŠXüÆõ\Ø¥.­ê%Ѿm'þôîš}>¸ß¿ÌLúc}ZÍÒ=ùE-–þ­­:ÞÌá}ªcodü½gpV÷Ft¨Ÿ@‹F 8íìx}æŽèÀ€µ‰©c.å쎴ª_‡“]Æ ßoŽN_Z“uö“·wȵ;ÕELD޾†œ~Ë=\×1 ÂëùìÉ'ø`mUƒú:„òW3õ­Çxê›Ø†‡ÆýûÑü~-ÚHíDÏ\/†dîëñì׋Ø_BiI>—ýÈW3ÖÜÙÍiê1p góå”mD³~âd懌Ølše¾vÝ ÓÈeàD §õ漋/æòK.æ¢3rúiH3ÀU¿Qù:…?òÅäÍ”_Ñ#””ÁKœÇk;«×––¿'X(,"""""rbQ`‘£ÀlÅœt!>3€Ë—À†<ÿ™‘ÊóÈIF­ÿÍÂD6å{Þ9øÓíÃï!ðßp\>œÌSÕ¨"¿PÄ®éÇØZ\ÿ.Ÿ…‰IB=?P¬‡Ç6àêûÿý™;™ò÷›xðŠ;¨7ùŸôZÿ ·ßñ9·¼Ì›ý3±·-¥¨^}¾q·™)ÄÙ½ˆùà[fn¹f™&8;˜óÃR|]î µgŸê8;Yúýd67»‡'é@LÙj¦¿:šGΟGÑG_s}› Œ¹þŽk_6zçËÜšë°ô?𷋇øp"lï«á:ÅÅì Pä—j `9ŽxpÆ-7±îÖ‡ødýÆ>ö¹O\JKïîl–¾1Š3ß²…­òž­†›Ú']Á­çdïwSl³ü­›9û=#êõ óõÏqW߸ýÞàÕaðµ1õ®3·pÿý>ÞwqÊÛÝH×Ô¾\0èæ²IO\ˌ缄KʈC“a¿£[>Lï9ëôã‚~Ÿqÿ×[˜úÜ9÷ÕDâŒE%1œrïXnîìÁ¬Ûszã¡IÛùö©ß3ý¥Düv)V¿çÍ¿´£}K?g0éñk˜÷//AÿÉ<ôôå´Ð§""""""' ݉ˆaŽccθ…£—'Ç1ã3ØRà¥aZ€¬Z{ÒµÎ*eÆŠ þ¸Ý›1¬C>†áŸJ錛±5®½™ïÇþ›yÙ#¹®ºÒZHÍN›ƒÈíKoJ³Üì=ç–“ÿ ÿ~}#½ùŠ? KÅZ>º–É]æóOÑÓ¿<'…ž=úÒ¾MÐþÀeöÂIž›˜8i;#F¤cfñã\“ö醿ÂÖñÍúrrßN¸€^ýÚb ìÇ+Ïç²g‡›ÿ)ÿze ÍGÍàÁ‘Íqݺ6¡äçî¼üÜç\ùÂpâk°NB%mëÝû „•F[DŽ/FB®¾ùlßþ.ËÖ|ÄSovæé+RHIO#iã6 aB.±Édå´¢[ÿÓ9£wc*$cq°Be”D½=óˆ88P!wKLã3yð©ú|ôÞ'Lœ½Œu;K°ÌX’ë6¢UÇVÔ²0bé0òH~…×¾ü‘å;ÂÄÖiE÷¡3òŒ¦ÄÖ†H¢Û ñ@曼õÍl–m.¢ÈŒ£vã&¤{Â8x0ŒdzýßCÜS÷uÞ8Û )‰©EÃD¥F*§\?Šõÿ|ñó7’ŸµÓ¼è²/"""""rbQXDäsmùÏÎé¸ãÓ0 8«s– I!b*¹×IŠ0ê´¬ÙCzâÞ4††é&ÆkR6ÿ œ.OœØ žÅè—ñiß—øú¾ø~IÖ:¾zñ~¸ô<®íŸ®“MjÄ4Áú…d[+沤´ˆõ£rh}Óî¹6VØÄ·µ×9#¹ºÏGâ ×p§ÑéÜQtª®BÞ:tq+ÝGT²,½+WÝו«t¦‰ˆˆˆˆˆœ¸·Îj‘#ËøùE<»Ò>ïÖ 5Ä;³Âô~Ì`ÊòŠ!žâ \øÜò~Ÿ'z;·7÷º °íJ nNMê&áÒi"G‘Û4jøä­džã€Y‡3ÆLá“ 3výÌä³ïfòÐÐ ok.}sŸ¿2ŠÜM¯ðçÓÚqñ˜Ùª+ÓH¥ßY§â™ò>¶…YõÝD6çžFïÌš½M3L³¼^å¬Á8Î/˱$¸7ísr¬žµ"""""""""Ç+€EDޤàNŒ³0ÝÑÉý‚øx.„-øpNÅÍ~Xe°z¬Ïƒ©+*FŒÜn/ÆÆ¯Oì¶q5çò}Ì+׶£3EŽ"«&`?~åFK]ÙmhâÝÆâ•õ›6'{ÏOSê$î Šñ4ì{-w½=±×¤3wì+ÌW]&$ ¼’ÁÉßñ¿÷'1ñ«¥42”F5‰±Z+™=s3¾Ü64pƒ+§#¹þõÌœºŠ=áÚÈrfÎØˆ¿UG×pÊä•ì 7¨åÕ‰$""""""""rœRXDä2òæãvU¼ÔZvy 4T1µï¼ÒP%o¯cÛ̃ª‹³ósFuÉ¥×_&SLI·¶§~¿ÇX°»ƒqh7·iÄÙ¯l¦<Ñ«ÃÎw.¡qÓ«ù¨°63yÌïÜ© 4§óàëyæû-{HöV&<6’a½ÚÓ´Aê7íÌ5ÿÙŒ]Õ|k1OžÒˆžÍaOçðz¾~âNïÞŠìF¹t9ó&^™»O°,¸’ÿz}ZgS¿Q+zžÿ8ß8Uþ[¦~ù:ãfåA­a\>"›•Ï^Ÿžz‹IS¾cêWoóÚfP쀵ú3Þ|k3çÌeÁ“˜±4’SI6ª.Óð÷æ‚ ²™÷üõ¼º —Ó†4«¢g¼Åæ/žâ¹·>æûo?æµ›/ã¹…8ëŠÓHŒäÓ¹êŠæ,s)wý”SÇñÚ-—ñü’\.ùýà¯S™•Ûƒ{þ>©qœN$‘ã”Æ9’Š×`¸*ö”ó{¡U¦ÁÜuVLÆÚ6ËÁëÛ *kºÜP¼ò +ccE,,Ûbèг3¾÷~bNžCë4ƒÈ²™ÌÎ ³éǹ/¯ƒŸsgÎÅi3]âÌ~ôb.}†ßù iîðógô%—øànmÎ~ÿkßÂ3t%9’™“†é,­|>yûÕ¯”£/â겸á3ºnߎ¹“»/¿Ÿ“Ÿ |!“þ—Ày·?˃ÍÜlþáÆÌvðVúE*ë5)ÏÅ\ÍybÔbÐ_þÎ7ÞØ+?&’CŸÛ;3¸s ºÝ;ŽçRïâ™·oáÿž(‚„,š{çtųmãŸ}†GWï$äN&«ÝéÜ9f-ÜU•™‚ ¹—Ü@o`ZÇQ Ë©ªû¯AŒw'Sž¾ŽçÖIÌéˈž`TøÝŽö·Àó±·ó·\ɕ۠v«Ó¸îõǸºƒÿ Ö©hÅÖòDÖ.Ó WÓH"""""""""Ç)€EDޤH¦×…áîqhO\?¬4Ü wt/à†õàù+ˇõlTo÷ûlo»¡,pPU1jÎ3³Oß3ítëCF3mV)ö³uÆ ÖÅ%bÌœÆüðit13uF!ÍÏëIZÑ—ÜýÊ2ZŽú†'®l‚ èÙ5›âŸOåŸÏ}ŵ/ %“„Ü>œÒ«ýÞ«ºù{9ù_ðÂë›9yôÜ<´Ðú‘õLèö7>þáaúuø’±ïm¢ãmïðøÈ†å),º%³ðÝoø¡ŠÇ(R—icR´ª]Ï“}Œ»€*,È¢ÏM¯Òç¦J6:é6^rÛÁ— ˜µš“^ ïÅçRõð¿&)ýà»:U=v¶+“ž£^§ç¨ê¡ë죰ÌbÕöòtÅ‘èÓÀ"""""""""Ç+€EDŽ0ÃkbÆV –dÆÂï2«Þ®k몗9¶e‡X¯ô¾ h{/LžChP+¦}ÿ3~ÿbŸûˆ)+,:y§2u}côo„³üm•ÖcP÷†{ƒNîÆtïV—'¾žËÊÈPÚ‡VŸÈŠ,.-fÝMhø§=+làßRDxå"–…ê1ø¤z¿@Yj¼›’ Ã±î-^ʆ‹)rŠ™7öþ—p=¯œ‘Žqœµ×”åÅX¶ƒË4¸²Wm@"""""""""Ç1€EDŽ '¦ŽaWè|Èå- _­C+ÄÌ¢ÿ)¹<üÎ7Ì+,bâ¬Æô»k8qSÇðÁäuœã™Àâzx°¹æ…0™ã€™ÎY›Ûíûòd—žŒ±lWÊ^¥w–ÃÀã2HŠu‘_9¶‰,㣛óÌ"7õ»_Æè—o¥•çøj«PÄaêŠòQµ·N"+Å«HDDDDDDDDä8¦°ˆÈd$7ÇZ¨6l;°v‹ÃÒµ6¡d¥ä64ñUc±ËB8‰Í±v.²‡ !÷Éwùß‹[™R»7×5¬CBÿ\üümÞpÍ!sð]´ö€»I[Zù_fú´µXrÊ{GV1}Æ&b[µ%ÛM…”Îý‚”Ý’fÞ—Y¸Ê¢áÙ¹ìs𴥕ÿ_|÷í2Â[âÑé%‡(5ÎM0lS¶á;±v\ÿåV®¯ÑS¶5ø&?å*Ž›—Oa™En]WõNÓ‰#""""""""rœSXDär›c…ãÀk`˜“ºþô³Í£¯…˜»ÌÆÙ§ckZŠÁUgº¹xˆW%±ãHQ14ïspuÙù97 º‰Y_ä‹û¸åÌ6O2ú™MäÜð¹nó´!ä>ò0ÿ´søÃ½mÊ­I§ñû+šrö˜k¹5î6Îiî°ø?ñô’f\3úÔ]ãÿ£Ö ®ñ4>{-¿wâÂ.õð–ndI^CÎ?·3 Iƒ¸þÊfœýÌU\ËÍ\Ò½Þ’™¬(qª}Œ"UžsÔIò°>/LØ²Õ •X¹-Èä¥E¤'z¸ëôL|¤â(bÛ6¡PˆòòræÏŸÃ1ÇôÜù‚U«ð}{ ‰}óÁÚš=7á»(|¡Wg‹æy&©I&†5u.k×;Ì[lcXpçï·°Ö~¿žº”?à´þ:AŽèÏÄλ‘œœL À²¬Ÿµ>.PZ¥¢ÖÆÕ#¬. óÂ7Ð:žËOHÇkî{æoûcúŸ”†iZ¦‰išß- Ãhø·a‚a4ül4|b`@ãÿ0 e‹¸[† Ñu›ˆˆˆˆü¼?[×{""‡9¥rˆˆlñ͈ä_EíÂç‰íÞtËË'Ÿàãäv>ÑïqÇÀ¨`¯@¨ˆˆˆˆˆˆˆˆˆa9TLN÷; "<û>1«ðå&ãMÃðmdrm»2DxÝ&BÅvË«q{\ªÌ_Ù-€EDµ&Çãdö£¶d&¡µã0~˜†å`šu`€ëx±IÂI芛=·ËIàU»‰ˆˆˆˆˆˆˆˆˆˆÈ),"òs0,Hï“ÞÛÆ0\\<à‰ÃT;‰ˆˆˆˆˆˆˆˆˆˆÈ>QXDäp`À àª%DDDDDDDDDDDä'Pz™ˆˆˆˆˆˆˆˆˆˆˆˆˆÈQB`‘£„À""""""""""""""G €EDDDDDDDDDDDDDŽ5ˆÈÏȵ¡j5FÕ*×_nlSŒ¤ö¸V@m$"""""""""""{M`‘ŸCÅÌ¥ÿÁ\ÿ fd¦ÇaX¸®ƒ`»NzÜVâf Cƒ6ˆˆˆˆˆˆˆˆˆˆˆÈî)š "r¹áJÌi7àûl$1Åã ²ðSðÆ$â $âIÆŸA0!`Í÷ø¦\õù™Pþ½PDDDä¨91,c·¯ðöŒrÜ£±¼}®_”o¿ÙÀ£ "8Ú;ä îk6³g—óæJûð<DDDDD€ED•ê5x>;•@ñ X¾X"ŽÉÇóqv¸ó0{u,kK}˜?þø b"ëð~y6æÚ±jG‘£]Ì”1òõªÚŸ„Š.æ¥k.â²- þP”w0¸6‹—×2¿Â=Äõs(\_ËÌRGÁÀ£SÏ /¯áÒ¯B»>Ü“gUðU‰ú\DDDDŽ^ZDäPmÀóÕùø:¬@Ûàýɬ)õsrçŠí/©ò0a~"—°´ø(¦'@ ÖKhÚu¸V7ûDµ©ˆˆˆÈ!änšÈW>Æì–¿ã¹»‡“iF•3ƒ¤åä—‹uX¶žÃÄñ<°Ä&ì€i™$Ä{i×,ŽszÇÓ5îà6fuqÿú¶’ï #l² ¼tj—Ì‹!Å©çÍqXÖ#‡cRõŒü¾±÷îZY½cÕ ÷IyÜ×ÉÂØÍ~`yL|´odx÷ú$€ýÀ0HKö’g¦Ç‚ˆˆˆˆÈ¡¡°ˆÈAæºÖ´ð¹ÕX¾8&-ã›% T×[\Øo#Æ÷:úµ©bẟЄŽ9uœÕ³ ŸÇ"—Jݴ뉞4‚MÔ¸""""‡„Ú c™éI"æû±|¸d(¿mç=|ªg6åÔ?ßÏ©‡ï1ÕµvV °Ê7…ødz7®ŠpÏ©ô:HÍéÖÕðè{%ÌHˆç‚!É4 À¦²z›&q†öìÁ“›Ä}ýbðmóZ|²‰±»ý  >Û¡tS=STpÇ«Õ Þ„?¶²~ÚPu†ÓOÍâtu‹ˆˆˆˆüÒÏÓÕ""—UüÞÒ)xâ2¶¼–e@Û*Úe×’kÿè=AŸÃÕC‹YPC(la™ OÕ¦ŸÇÄYðNï‡Ô¸""""‡BhcÆÒå¿pÜ·åå§óë¶}‰ßárKùæùÇy}ú K+  ­E/κü ÎhÛ ¯ç«WþÅ«_.`]­Ÿ&m›ã«vÙ÷tË™þÚ³¼öíbVWö¦Ðïʹep †SƬw^ä¥ñ3Y± Rš÷dÄ%¿áœ®É Á2g5¯^{_ô¸—ç/iÝù¸‡òÜÊ)<2ú {O^Ñ•˜CÐŒfŒ—ŽÙþ†²òbè“æò›7«øxu ½Zýxù‚y¸mR…!OŒ—nRÝ7†4ÃeÊ'kùˆž½0‰æý°rÊ:®œàÁËRéÚE´KBÌ«÷pÒÀTÎÉi\°y“¶+ÉeáW ù Ààøùüµ­®ÍÌi¥¼0¯Žåµ’ä´)œ›×¤tm¦M.å•B¬¬p[ýgs{ É2ej¯. ±¢Ú%93–s¦pjæÎ£G63à¥CN`¯÷!3ÆKÇÍË7 2¨S<Ç|´ž¿ZB§¬LN î¦]÷Ø'a^~u=Ÿ7ÏâÅþþ†c!aâw¥ügQˆua“¬,¾xõÉ&""""G1€EŽX.ŽecÑ:æÍ›IuE)];µ#3»%Á¤¬ÆŒR£ñK~NÆâçð·{­mVðÉ÷ж 4Kmø]؆1s †wréž_ûãnæÚ18]n†@ºXDDDä ŸwWLù˜¯éÏŸ¶¥]r^ûûx&nìéçÚn5«,¤<ÿþtM+¼¡B¦¾û2ÏÝÿ¹Ï\C¯@-3ÿu'|ˉ^ÇUM-J~ÎëK¶ W²dÚtвÏ㦫:ïTcf'afñ«wqÇXxÉh.oê²ò‹ÿòÒ]#|ÿý\ÜÚ·“*ïEy8ضíü|³ š^?.õöÎëœÏoOI Í%k*xjÒFþ™‘Ë­­M:åð-©c^uÍãl®àÏI¢Í6)¤V¼‡lÓfÆ¢:Š›ÉÜé¸À­zfò§± à²hR·Í†ÁýÓ¹"V.,ç…÷‹¨?'›K›@üŅIIüyH€xÇÁL¶0p™ÿmw.ñðë2øC¼ÃÌ©¥<ùaM.I£·ï¨;D°—͵†¹/—¡¦‡¡}xï•MŒ_ehWg—íºç>Ù‘ÃŒ¯Š¹w±ÁÉ}3¸&JÖWóZ¡À""""rtSX䈻¶vq£µ|??ø0ã?™@Uu-XM3RèÕ§=gŒ8›‘g›Œ‰§áŠ\àŸ….Ç(™¹Ó߯ØhpËûpþ±.×7Në»|üm”V\yÂÎo†y=>ìuŸâ¶<_,"""rPOÀ‹ù|ül’ÞM׀瘓œr;}¶ŠSÎo¾Í<£1yèÙµ5隺Y7Ldúr›žM§2æ‹2Ú]x'מڤá̼cË?›É÷ÛfÌïʱ][oY¯[=·?* ù¹1zd.&еc6u«Góö{3øÕÍ}‰Ý±ÊU{.ÏHèËMÿî{¨/fG]LÛ¡¤,Ä'ßU²Òà´œú›C¿Æ·Í°XùÃ:>)Œ`·öÛ4HW³„É«mNëdaDëYP úl{>ž”ÀõC#Ü3q—,÷ѧ}#»ÄqLÒö™¸¾XÍÓ¼[^sCµ¼5'B‹cs¸¾›è–㥶doͬeÔˆØ-í›Ãqyþ­}ªáíy6½†dqq›†yp[‰2íÅML\çÒ»ùÑum^¾‘ÓÛ¸åg+%‰'/N¦•ã²®FÁ àßõ:Ì$?­ü0½,ŠÝ8ôÛuïûdË.WWËû £´ï›ËuÝ= ý›k²üûZèÓMDDDDŽb¦š@äÈÐpÑì ×òÈß┓G0ñãq¤˜Ú7‰'7Æ"+ÑÃ’ù˹ëûæ?RU´gëm 5ä¡î·ÒyxÌí?jmžžhpßÿàw¯BĆ6[oe'A¬žûn~×à¡ +K¶¿Idzc0Jfì[]ÊÆsmïvô¿íkª©gâÝÈô ¢ „'r}çfüêßE8ûKÙѼõoS D øì¡+ѧ#-šµ£÷é£ù÷ÜÊÆ½ÊfùË—Ñ¿skšæ6¥e—Á\üÀnNp6ðŃ—qjÿn´nÚ”¼Ö=¹â­ULøc'ò†>Êâ-£`Û,~d(M{ÜÆ¤âmë+"""òó°WOd²{õE®ÐŒüÔ ©A ¯ ñññ¤$§à&æ°,¾_L]È=wÝÊݪÄìœÊ̓|$aì'™´e ‹ãÚÇðÔ'ÕLª‰¥Ãš:6¦ÆÒ3~-âñб]Û%rÆ´"þ8©”w[dsiÊl÷m ´rRÎÏÜþÚ+&öè Dš~­2ý?š¸c¯LéÐØŽ†E3¾ÝU—׳´rRn̉ç½… K¹ä¼tŒÐ¦Î5éqkObS2¶«¯ˆˆˆÈ!šÏ§_—Ñî×wqMß„­çÓnß<}7c?›Á=ú¿‡ÕX¹-iéËìÙk‰¶k¶Oâ ïÇüE8írÎ,íõÌÿ¾”@ó–äZ°c¼ì§”wP¯kÚfý8P¸-·1T.©g¥àÚ>q\“Übíñ-â(æ“E!*VDÈoKÞ/z š5 >©’‚ Õ$àš‹[†¶’ý´ôT2¿ Š“Õ0Ü0N”yëlé~rMvU´’|ä[Ë7Av{ï/öÆK|J€®ÛØwñà¬a¤JVøc¸©µƒðÎÛu?údó{f¯ŽÍòé&˜ˆˆˆˆübèÜWäáØþóâ‹tΊ'Þ­#.>@|l Çôãu½xÈR³~%¢Œ¾îj®øíï9éÔ3HÍlJC òÀ„£¶ËKß•0nÞ&¢ö/ûyêý |Çù¡{^ÿ]×!RW‰ áÒÝë Ä“ôÑ#ÿàÕÛÈ8!]þ³_Ï!<¬#“¿]L÷«®!øÌ¾[nÓÃ7‰IÍ2¸îòÿ²¨¶šµ£»“Ýæ5¸Øƒ˜â*\âYóñ£ÜýÔ8f,Û@? µÕ³~÷uHÊiýoçÏ㿤ô×ç¸p 3j;séñi¢MDDD~v5³'2©º œÔ…VÛž¸´âí×¾dRE?NNØÃyWܱŒ™ÇoßÏ=üšS:¥ã«[̺ОÏ#¸Þœ="—›Þú;.`H¾ËÊÏ_ãõ5yœuU¯Íyº·å¹•Sxdô,<ö&ž¼¢ënƒ²‡„a‘€â55Lïà¥gŠ\*ùhj5ém|$šQŠv<µôÑÑË53KX_ïãÌ“½?Ê(ŽVðð÷.ó|d œºSfW±Öãç¬ L/­Ó Þ[¼‰²hîFÙ 2(+È9ݼ\?ux“š +–óß/¿Üi»oÙ”˜ gwöpóô Üc&1,Ûƒ7eUÈ˰~‚¿ôkÛÚóÖ†ð9ååõLù¾Š/Ë,NžÆXvX7ûÞ'F ȹݼ\7mw‘ÄȾHˆ‚°>ßDDDDä覰Èa¯aßêŠ2æÌ[B‡X—¯x¿&×ÅKË00\°„ÀA\bACQ_ï4ÿ|`TÖÙÜóÑz¬«S÷ì‰?×´1|;oÿh] ‘0Žù3¾–§ax‚DÖ}CÝÌ1k×àKHÞù^a»`¥ü´º™¹ ÚŽ{ßøœy•U|9£9ƒn=“ØIóþ×k9Ûû‹r†pw[ÌuÁÌà¬Ç^ç]·ýÓa›‘„³øI®¸òE¬ îâww%ÝXÁËJ_r IDAT¸’÷T#…ÏÄ_FaÂÆ3éñÍ7¬o;’AÙš¢^DDD~îÓðJ¦~1PÛ è“¾ã¹œA“ãúÒö?¯ðÙ·%œtÊžVæ£ÝåÞø—ùÏÿ^à®7ª±½ñ¤çt¤_^pgê~Ú_x;wú_ä¥wá– HnÞƒóþrç´ñý¤òÜ-ù¶‡ÃÃÐã™ôy%ÏÎ¥çñ‰üi°ÍÓK¹u–ƒc™ÄÇzi—dnÓ^­º$p̬f7IdðNN#–I|moYAQƒá³ÈË rՙɌLh8Ÿ=¡*ó>)祱EØ>½úú˜å¥Cß&Üí)å_Ó7òi-$§Çpþ©üºÉž®­Lºß„¿Å”óò‚Rþ:ÙŸE‹6) hï'ø‹}ÒÑ 6ÆÄZ³‰[ÞÞ„é1IŠ÷Ò.?‘»NI O²±Ç÷ï{Ÿ´ïÛ„å¼4¯Œ;¦ØØ–Ezr€)¦:‘£÷ìûÄQ¿s7_ú5üç‚ë⺠YinÃ?p\×iøÙql\ÇÁqœÆï6U›J˜8þµ¨È.Ìš5ƒáÃGî×{`ÃêEôé{öÌã”ü’büÄ[‚¦…×gá±B¼N †pZsbN¾_B‹-gÁ╌:ïb só˜Xû™[QgsÃ[kY¿IL»Žƒ ©­ä†Þ•sÌÇ6ÊæâŸt&Y?ú]´º†êºdêŽ}ŽâJ¨ªªÀï÷‘–œ@ÒÒûnú”@zƺ,Z]Mmâ©8ÝïøIÛ`/û§ y“×tgüø¦¼2áâÿuC?éÃo¬ø¨ÛÛ|y[wà«[»mÿôQÝ×ÜÜïR_þ"'þï7|xâÇŒÿc›-ÃïÉÑû™Ø¹s7’““ X–zühÓþ˜þÄ'¥aš†ibšfãw Ã0þm˜` ?fÃpøFãœõ[þ^†nÏŠ¸îæ0®ÛD¶?1ñÌ+(<.‡;Û[ 艈ˆ‡Ÿ­ë=‘Ü2€EŽ ¹6N8LÓÔÖ–UóaA1k+Âôm–É™]ÒI±¸¸,+«åëyÓˆ»–_]{3mÚ4cåê5Ô×׈‰ã§£¶Ë}­Wðw¸‰íˆºAŒ€anmûʸñß&ó #TÔ\´Ó÷ú¼-’ã¹ñèÓ~ûŒX»¬·É€}«KÙxFÍŒ“žãã»'°šäôÎsß…´ü¿1´ó˜˜'ŸB»ûïåi§%×Üѹ!Ø›2Œ+Îÿç=õ;®²®å¼Þ9øj׳¤<ŸsGõ$¦U{Z¸/ðê£ïÐäôv¤z Y[µ—ù$1}¸ð¼fŒxözVW´á7¶ÄÚE}EDDDDÀ¥hc˜Z×eÑœR>ñ%ð÷6 þŠˆˆˆˆˆl¦°ÈÂŽDi›Ez|€pi5ùmò±ªjÙXQÍÂÂôÈŒƒ@*‘ú¿‹ ÍžDÏ!ÃHJŒ§¾vsxÿ½ô] ó5ìó¾±üØYɆ>×–¸åå1ßDùf™ˆîò­áˆÍâ >žüÈ¥o­Ù‘nÔ&Žƒ}  YBÛÕ¯gžÛ‡‡æ–0òÔ òOáÔn÷17z£:lþ3O¿¿¼ÉRîæ¡7n粇«!>›§Þʈ³!¾óÕ‘Ÿ@`‘#€áZø “Áùb©';>– éâ:6©™ ÔÔ‡1A,Åsϧêû¨Šl$£I*†i°fÍ2rZl»Æ}*¿*dóíÒª=,e’™CV¸Ž¹¥Î¶÷`¬óçá´»šº©K1~E°sW_àµ1õßËË)½$&lß'¶ SçDyï“0yY&YY‘¢ ª–{°üLï‘¿cG—óÎ £xd¡‡¦}ÎãÑ~Og¯Žw‘ŸB`‘#é£EßS˜6w2IÑ(1>' n˜ëÁcxð%T[K$ÅŒFðz=`TTnbݺõtìvÜ~?me {LþµüjD6-g­bn©s`¶û`¬óçb˜8½¦vÞ=D¿}ƒ>]sèÿ·Ø]oº}{xèÛñ©[°ŽÚŠf8Ç?1MŽ’¿@ýñRFë9`9B¤¶éJ||"õáj"†‰ax/8&ëê,<®—I¯¿Efz:]ê£TÆÅY¾ž¬ü<2šd“™¹yøç}ŸÿwúÊuÀ`zpºÝA¨háÙ÷ˆY…?/Oj,†ÏÚnQ×v°+C„ו*6±[^{Ì¥GG毈ˆˆˆˆˆˆˆˆˆˆ4 ‹ö\\ÃÀ°ÛoÕZ"U%8¶Iu$BábÔW’—Bs«š¼nCˆÆ9””ÕSõÃb¼q1¤¦ebZ›÷}Ÿaµ¨2²—K´МqêýÝøeÜûƒ ¦—^½ÓøuÛ Íâ bC ï~½ÿ;¸˜´è”Ε=ãho­‹0õ»u<´(Ò8äóŽë\Χù͹-}x½”Õ™Éù½óy¼S·ÿ{ó7' úôÏäÜ~râ-|®CQa5ã&—0®ÐÞ:¤´á¡[Ït.éKó ”o¬á“6òîÚ(În–÷m1ï¬Ü.mr{!§ž1 Œ}Ïüݬ´:º—Kº¬˜µžGFqp©­r“Žýrøs›ï|½žgª,º÷ÊàŠ‘é¿R̬„dFŒ¥tjZÅúˆ­Žn3ßïŽët¨5ê·¡S,¬®°h—í#¼¾”eÛmÑ,/HjY)}"âñÒ¥K*—é'öíµ¼±±¡MÚöÉᎮ0qR/•B~ûT.9-ÿ;kxµx×Ë\xj6æKyeÕþt«é½qÒ{à8õ¸Ñz ÃÅ5¼`À0µû‹ˆˆˆˆˆˆˆˆˆˆÈ>QXäˆ`ãbaxüØŽI=6ñ‰qÑz*ªk1BUµÔú‚x:öç‡IHÉL&/'‹ªåx|~¶þî[0¸2dïõ²áš0«J#[¸F Ž3:y˜õåZ^_Úu»|¢‡—¤Ò?{s¢ 8Ì.¨cñF¨ßó:×V³ÀΠw¾Åÿ¾·q=:f¸,ž"´“:Õ–Õ2cM˜µº÷¼šWÁä6» 9»µ5|SW·ˆ%ñûJªÓ‚´÷Ö3nMtÏT»žY6gåÈ6«Y …'Ê”‚ÈÖážó×G9¿™Ÿ³Š»XfÁú(gfúh¢D]9L(,rDhÈØ5 ð'7!kð(füçQ*,h“‘@ÀcòzIëÒ 3KLf3bccøvÌ'ŒºàÿýäÒSã<{ÞYôÕÜ(?[Ç[ÅÛ/ªµqm›±cV1³i#ŽIæÚó’9mò:n›"¼«ub3}q Ñãé¬bIÓ i¥U̪ÚËÖtÝý [DDDDDDDDDDDä°¦¼5‘#Ä–á HëØ—u‘ Ó‹kxencá÷˜•T0ú,j«*lóÌ#›•èÝ‹ :ÔG!è7·û`±7Õ³Ö¶h‘Eåa ¶ù*Ù2Ú³Ãú5›xþƒÕüe¶MÛ®‰t°v½N€ª•| 2´]^Í|¬Y^ͺ½?ÙðÒ>ËC}i=EØå!VF=tÌõn-ÃôÒ)ÛC}I=ëv³LÇlõ¥aŠíŸ"""""""""""rxP°ÈáÆuÁØBÄë¥i Ìg‹B„cžk°º$BÈòÒ9Å„M•»»uÚ¸Ñ:>^æác2Éò×3ö³0»ŠÃ¦µHæœMU,©‚ܶ©œ›a·5ÔÔWóÞ¼0÷õÊâ÷‘¾(5hÖ>•sRÃ|0±z·ËŒJ óΧÕÔj¯‘ÄÀ"‡÷! ëN$ĺ5«÷áÆŽýˆÕë ))*â™ßžÈÙ]òX¼¡Ž"ä‚x¼ºviO0Æ”WUðê?Ÿ!5«)½ô'!!˜Ø$,µedc/ÆBîÝ<Ë4°Ý¥ØÚ|ûÝ:”Æ…§Äá G˜1%Ä·…aæ}SÀßêÒ9¿C:·kB8ÊÊ¥%LZ"ôÓ·G2—%Zx›¢ 5<ÿi+œÝ­ÓÆÅeÅ‚MÌéžI×â2¾*ßuÍ"¶E÷ž™œ›`RS^ËGoàÕ‚Íáb—Å“×qW4‹{dñ· lÚXÃ[nä"w·Ë¼=¶˜·‹]í³"""""""""""rØPXä0ãºË˜Ã÷óæ±fu!“§Náó/¿aSU%`àº.~‡õx‘猡YJ†'HëÌ4 ×ÁÅ Wï^¼þßwˆü°Ã%ÅEX®´Œ š¶jENÓ|ÒÒ3ØÓhðñ‹~­âö˜ÝTÉ“oUò䎿p¢ÌœVÈÌi;ySa·¾\¶ïëܺ056‘UlØM¶bu þ.„½ËF2{Z!³§í®c~¼Œë8hôg9œ(,r˜1 “—_y—xL0Àp€H0§®–€/ŽZÊjBý6‘p-N‰K =“P(D\B€ÿ¼ü……ëˆ Æ²båj"Ñ0®c’Ìý†Õ01b]»vß«:e%z9­[ïÍ*ÿyÇô1hH.祹¯«ä±ÊXn™ýFY’ÓÓiÚ¢5=úõ'+7O…ˆˆˆˆˆˆˆˆˆˆˆì€EC~¿Ÿ÷Þ}—c;Ž‚‚u /:.†á.¸ðâßÇwñY¤·kA}ñʇø°KñÌït=‰±ï~Η_Maåª5dffP®göܹ¸¸¸äææñܳÏãóùöº^—ôMcé†zæÔþ|ãÔóÆËxco–uëyý¿Kyý0ë_Ƕ™øñG|ôÆk„*7ƒßçÝ2s=°nÃz–ÍšÎØW^¤E§®\påÕä6k¡ƒCDDDDDDDDDDDvK`‘ÃTVVÇ)#GRPP€Ñ0 4`€õÑ(O¼ô6ãósÚ§¹)‰Ôl¬¢"aÒû³lm5µõ8QÓãañâ•„Ba0 rssøßÿ>"3#sß>0,ƒ?Ÿ’Åo¯e]yX´ÂõõÅcY×§o½ù™™Mö«N‰1ÊãÞÿþ¼™ÀG Ƕyòž¿²|Þlb~\×Åi ö®»]Ø×Åu] b|/>òq tîÙ[)""""""""""";eª DoMš4áËÏ¿à/·ßN\\†a`š&†i`.&..˜`X` _¦×ÂðË⦛ndÂ'Ÿìwðw³„‹»ÏÌáW=’ñl.Löèóq²xæt<–Õäu]ÇÁvl×mø¾Í¿ÇÁq\×Áu]b|^^|ì!Bu ¼‹ˆˆˆülÜ|÷ï¿óø§ëqÈú*Yð¿—y廢³>ùÅSXäà÷û¹ó¯w²dñb®¾ê*âã0 0,L×òàX^ Æ£a¤h’““hÞ¢sç~Ïßþv7~¿ÿ€ÔÉcü¦:ÿ¼¨ÛÆc™¿Ü@ðÞl{¸>ÄØ×_Æçóâ¸nc`·ñ»³M x›ÀoC†°Ó(nX¾fS9_}ü?""""‡Jt1/]s—ýkõNK¦NeAq]ã-àÖ.gìc7sѹgsò©gqõkˆ¬|‡?^ð;›^»e¹rË™óñX¾\VµûåDDDDDDDö’†€9‚d5Éâ‰<Á}÷ÞÇøñãùò«¯˜3w6kV®¤¢b`˜˜DÓæùtïÚAƒ3lØ0âbã^½Ü8,‹«ÙL[YÃô•5¬ØXOym”šú£3‡!Öo’ôÐ<ÕK׬DZ$%³bÉüݾgæ¤ï¨­ª$àó5tœ†aŸ &x6ØHnÙ4Êòµ&ŽÃvCEû¼&}þ)'Ÿy¶‘½âR½t/½<Žï¯§"â%!£)žÇïÏëNòžžå3ƒ¤åä—‹µÓl–¼ù0ÿœ—Ç¥£ï¤s²…/=«>…œœ2â,ô£’nÝZ¾}ÿ]>üf?UáĤѼSFŒ:‹“Z'4–÷·û`ôD8Â×371fI?T8Ø>-rƒŒì•Ȱ&¾vÍaâøXbvÀ´L¼tlϯ{ÇÓ!¨£FDDDDDŽl ‹âââ5j£F:|êä·Ü.Áí~1ý`Û6¡Pˆòòò=.;{Êd|^ï–Ÿ‚º4dj.Ð0¤·Ï ¿:)ÌÈa^ëcìDî© +–QSUMl|œ‘=p+¾ãñ¿þ“™C8ÿê ÉwÙTð?˜Ab÷&âh6åÔ?ßÏ©»,`ó¿ßHz¿?pæqí¶¹ÈÌM>ðÛS5—ço»—÷6d3pä¯Ý2«r5Ó?Çã7Oaþõ÷p}¿Tø©Û} ëªãÙw6ðN¥‡AÝ’¹.Óª 3}A%¾UËüaYÜØæP]ªkì¬$$`;”—×ñÑ´R®_å¡ó’騻%"""""rÓ%ˆÈ!°fùRLcûÛY. `\°L—^lÎ!+£!sú‚SÃÔÖ¹|6Å»ýÊ\—¢ukiÙ®½VDDDdìU XP“Ɖ—_ÅÙsx{lj[–p(ÿ w¼6‡¢ª0ž„\ºž| 8¿i&à¬æÕkoà‹÷òü%­œ솩Û¬ÿfNyÀ¢í¥Oð耙Ütå{4ýË¿¸¶[„å^à©·'³dcOb6}.¹›‡d4<J¾|”Ë>)aCÈCj‹^œuùœÑ.v‡€hˆy¯=ÍûE­¸ì;ÕÌ×øzo îO»{næ‰gþͱ]®£Ï¶Û­œÂ#£Ÿ`á±7ñä]‰9¨½à2wR ïVøøí9M87}óV9¡},íÆòøe×4ãý6ß~½‘×V„Y[é6L²³ƒœÑ7™Ó³· ;Q¦L-ãÕE!VT»$gÆrîÀNÍ41Ü(_O,ᵕaÖW;„0HKröÀTÎÊ2·¬ÃŒñÒ1Ûß°íy1ôJp¸øƒ*&¬O¢cSceìe=]›™ÓJya^Ëk!%=ÈiR87Ïjœ“ËaÙü2ž˜VÃâ*oŒ—>2¹¥ƒC‡¯ˆˆˆˆˆì'Í,"rÙv”ªM;ÏŽ º íáëë¸îÿ…Èʰi cš.Wœæ¼SÂXÛ|Z›¦IUŦ}®‡[6žk{·£ÿm_SM=oìFÞ Ym\ <‘ë;7ãWÿ.j¢—²7.¢yëß2¦°‹øúñ«Þ£ M›¶¥çð«yâÛbìÍ8…Œ»í\N<® ­óóÈÍoO¿óîâß/ßÏïFö¥Cóf´ì:”ËŸšÆ¦-YÍ6Ë_¾Œþ[Ó4·)-» æâ¾ ÐÞa}ºÒ&?Üüvôq ÏϬlh¥í¶IDDDäǬŒl²Ì2fMœAqdgK$·?™Ënø+>t7Ÿ‘ÅÊwãùÉ5û0'¯I““nàŸOýƒçžzŒ[NÊÜîbÛYý!ýs±'_ËC<È}ÅÖÉÛøüy}¹xô­ÜwËe ðÌâ¹û_bFÝÅÔÏgÂWI9áNÛümäÉâ¤ó†’U5 Óª0÷¸Ý¶mc;‡`æáh/¶Im—Äé;„5-/ÃŽ‹#;TÃ'+l\lV¬ Q’’È-gdrïˆdú™u<ón1¯mØ2ë2ó¿-âνúgðبtFúëxòÃ2¦‡¶mUAˆ²´Dn9£ ÷H¡¿QË?ÇmþýÎy1¸„"{SÆÞÕsѤ"n›¡y¯t8+sRüö~/5,ã”TòàµÄvNçÑó³yàäDNÌ´ü‘ŸDÀ""™ë6dún¾‰“šäÒ»s”v-l:¶tHŒoœ+y‡{oQ"ƒ³N Ó&ßæ¯ù)¯lÌpög~e;ºù&ŸŸîýzx{6sÊ]:¥D—NgVy„™s©¿´ 1„™;}.n·ëébÖrñ pæ-Op[[—Åoýû.ºˆÐûc¹±›ÜM,™2…oã¹G;ã-ŸÍKwÜË-÷tæÂ›oåé?Ųñ³G¸í¾?ðHÏo¸ëX/`’Þë"n{êJ²¡ðÛ§¹ýþßsWÇÉ<=2có:Ûý‰§ì‚¿vŸ>uw^~­¾yˆÛm“ˆˆˆÈYÃ}Íî{þ.›Ú‚c aİAtÏ 4žŸÄ6ëN߯åÛ´ŒcÕ7×1aIv¿¶{yÑlàIlB~~þ– aw›à­S¹‰ 7Žc:u¦}ËÐr‡÷›Ä·:–z5dwJ.dúõ™¾Ü¦W§­9ÇNiu&ÍÛ´À·³ üf­hé°|m1îÝo·‘З›þÝ÷ôS¡ Í›øw^ï4?­,XVÅiì•`j€cóýX@Ï|Æ ywfg ªáíy6½†dqqã°Ñ­‡D™öâ&&®séݬa½Á”½›ú±Ð-.ÊŒ7ª™ºÁ¥wîõ‹:””‡ÿ]ëc‚\žmà†ª÷®ŒÝÖ³–·æDhql×wóbÝr¼Ô–¬ã­™µŒ‹¿Î¦“¹1tÈ0`§-$"""""²o9”ºüí÷µÌXÏØÏãû…A‡u\xZ†¹5ˆ9óû ïMHÆu!35Âç–púàÿþÀ4̼¯Œ”<1kÄ–ŸÝ㎧;÷1yF-aÃÔ©¬MÀ˜>™ù‘“ém,bÒÔJÚžÓôªO¸ýßKépíç<ô›VX@¿c[P½øDž~æS~÷ìHf6ˆmÑ‹}ºá¡Ù«>âÓ'Û0ü¢ ô½`Ê»—0uêjìc[aaÐ~ ÃG³îÚéÏ,soÎZJtd¼›×ÙªƒtÃC?úf­å«ïðÙüƒúl¿M""""?æ#oð5<Õï\MžÈ'Þã¯cޤݯoâ/çv ΈP8å-^xw ʨ÷Æã­³±ÚEÜ9`ûaœÓm ÏÝþ~8ádN=e(ýZ&þx8éFfF™fUì°”{À¶ûÐöÂOz\Ïã£W®Å[këYçi^feÄ¡hÂZ†Mغ˜mƒ¿ÆÙiYf¢‡&†MEhëkáeñÈÆÍgˤdÇsÝYÉô»pßËøq=ëYõÐ7׳5#ÜôÐ5×â?+ê)pbi›ϯ›ÖðÌ»,iÏé]ãéŸaírßÙ«Ë(5ˆÈAþ õxðx<8á(-rmÒ“¢tmSCûVÕÔG ‚Àµa›¤Þ¼Ìzþï‚B6VZ¤ÅºxM›¼L?®ëz3« IDATâüôYÚŒŒÒå/<ûõÂÃ:2ùÛÅt¿ê‚ÏŒá»å6=|“˜TМ!ƒ›á.{…µ9 ë³5«Osú—ÅCŸÍeEt$Ý~tÑ"3+£¦”’Z °ÒÉJ‡)•7ͬþøQî~j3–m ÎŸ„¿ÚÆêY¿Ëz[yÍÈ3Ë)+WÖ¯ˆˆˆìù?GÑaੜþöŒ~퟼×ûQ.´>àžÇaž|97^ÑŠdc==ú“dáÞ|θãzÏù‚?ø‡G¿Ïý•ûFµÂ¿³ºš;úb¦d“ã0oé ÂC»þè½ÑÕËYñ“›±%à¸«í¾¸Å¡ 1šq^r}0·¨žpǘ×»$Ì rR<˜['Ù¾Mv<×4,†œÔ„ó3·ÿEL¬…±“u†l;pŒ7/™GñGêyã£Rfú|tNÝ:Gð¾–±³zîñŒÕòq晹ô^]Í3+yðµ Þëׄ{ûwºoˆˆˆˆˆˆìÕu˜š@Däh¼ ®Cll„kžÍâêg²¨¸8ànýJK së«\û¯&,(ðâ±lþ­·Lóܰ3s<´¥_}μÊ)|9£9ƒN=“Á]WñÕ×kYÿÕ,ʉm=€»_Y^¯õqÜÆw>¼ÇmÈš°?ÃW¾Èº.×ð7ÆðÁËw0"w÷é(†åŃƒíh·‘ý ÿ˜Žd¸Å¬+v¯ZÆ*·#ÏL÷Öù4kÙŠœø½¸TÞ×s#@v÷S¸òÎǸ÷ôDý”…Ñ}­zNìŸJÙÄwøhÍ“ÙF‹øìOY׋¡½v2‡ìöÛ}HyœÔÖ¢tqcKw8«t¢L˜ZE?ȉ-v1÷­åûBªl¬$ù–ÃòMâ¥é6_éû55üZ¤ûh•Ïu§$ZPƃ3ÃDÙÏ2v¬g²Ÿ–ž(ó ¢[w'ʼu6t?¹[Ó‚ÉÉOàÿÎÊæc,Í©â{[Gªˆˆˆˆˆì?e‹ˆþ@€ºú:r\pœ†ªal}m[îÖ×\Ç\¢§ñW.¾ÀȰhqÊ)´{øMÞ{nߥ àÊü&ÄnÇÝã_çUkÙÃo¥“<­ºÐ1æ¦L^ƒÝ£eCpt%S¦ìØ…`?nRÕ/šËb§7Þx’ pâi‘¤g“DDDäÀ‰,~ŸÇ>«§}—–d%úpª ˜öáÖúÛqz Þê|rÜøß›IДD«Œ¢šÝ<úfÆ“ïR<ï;f¬Ï¢w“=×Á)œÎGó]š5O#)aîÚˆO ~Ÿ‡aŽ¡ûEWsÚ¢ûyþOfùiÃ8®U VÅj¦OË'?8áºKhìq»ÝÊ)<2ú {O^Ñ•˜ƒÚ &ÇôMãŒõÅ<ûV!˺ÇÓ7ÓƒYfúü Æ – 1[O‹7.¯àµ¤8:ÄÚÅå¼¶ÑÃð1ÄÄ9»³‡›§oà3‰aÙ¼‘(«B^†uðÜs’¹±g×N-å½–M8'eïÊØm=AÎéæåú©xÄ›ÌÐTX¹°œÿ–xùÕà ±€³©–±Ð<ÍCÀ‰2§Ì€µû†ˆˆˆˆˆÈV ËÿoïÎä(ëüžêî¹'™™LŽ™$ä„$$®ˆ.CBÑ5D×]õ·.¢«FtAAK]È.BH ÈÁHÈINf2“9ºëùýQÕÝU}Lf&sçýÒ0]ÕÕ53•LÏÓϧ¿ß@((,Ò~Iñ„•Ü„—$ô_—oTC“£IµÍY$ޤ?µI›vÅ4}l“äJuû¼q­µ*..íð×`w/Ö—?üe½pömúÛwOU™¤È¸y:úZtÓVMøÂÚuäœs®&ÿàûºÅ +¿5Ý[‡wð9ºâ×E?ýœ¾Zú5}t’Õšÿþ¡~¶î]¶è,ýßN\—‰S4ÞÞ¡{ò€Fœ?YC¢[õn½=¨ï 0ZP<6He{Õn{HÛßo‘)©Ô¨ÃOÐç®[ ¹ÃÌðèkWìÖ-Ü¡ki-RyåHMª)Í]j†iöÇÏ׳7ÿ~ùèL÷©G§ñ½ïèéÿ¤Û¶Ö©5R¦gêò«/Ô„ˆ:\IlÍÐå?ü¡¦üá÷zdɽºáwûäUiÌ‘§è ‹.֜ɃåȪùß·ê¼/=µ¨†).Öç/©Õ”ç÷êá5{týrWn,¢±#KõÅUhnM$Ô¢¬ÀqµrÅÝWgUVU¬ùçVéG;©Ñò1§ŽÐïÑݯîÒuÏZ© ¢ñGTéƒS:KF“>0Dó_ߦû–6höùeíúmFGž¾S:õ }éîëô…Eÿ%4ýóºiÑv}óç×jáíõrc¥ª6Q3Æ–éì÷bT<á ]ñÍ3tEÞcŠ4áœ+uã9Wæ¾Û£?û½ÎYqìBý莅©=áûý£†Ïךïo]¬ëo½¸ç—Tzš¾ýàiù¿«’1:íÒ¯è´Kâût¢¾rlj=û·QP Ù§ ÓìS|ìàq•úñ¬Bå]øÄ‰jæ C5ó„\whá¥cµ0¸«°LßýRòí‚Í»h¬æe>,V¤+>=6}ÍLŸÃ¶óë4wÂ0wBî» j+tÃ?V𣠠K@(-+“µVû›¤ÖÆýŠg·p.­*’ÛšP¬ÐñÖN²Þöìò`'Ua'Z@›ª9úéós2ö:ª¹ô·ZœtužO™¢k{G×vÌû/½œY‹®«þç]•þ®5mÁ úã‚Ú}N ºH¿Þp‘¿‘ë{àÐE = ¼¢R’´{_vliVí¸ôÓï+kÔÜш!rŒT÷vL·•éâÙë‹z¥MuMzkË0IRIi™"ÑýÂì¿ÝñíØ¢³Vôƒo®¿ÎÇÿeÿxt 0ô€ª¡ÕJ¸®š[#ºÿÉẲj»ŠÊ½*Þ#FíÖƒK'hé‹#$IÅ…qqü&ÅœVÉ•­ ­x%ªÿÛ0X’T:h¢Qž¾Ð?d@Ï"A€P=¼F®ëJ‘ˆ–¿Q©ñË5ï”:G5²ê}]yÁ*mÝUªæxTµUûTKH ÉM¸zk½tçãcSëÜ6‚ r"€0ò°1J$\ÉïÜü»eµjnu4ÿ¤]*)wdŒT[¹7ýWjm¶zõÝòèXíkòž®]×Õð‘#¹  '`èÃGŽ”q"©m×5zpyVo*×…'lÕ”1MŠHÆ%âV;vG´xåP=þêPÅ&õ¸xÂÕ¨±´Ð¹@(-+×'.¿R6Ϻo­±ªS³ÆU4ª šÐ–=ÅZoJTrŒ£yÇ„':ú'rA@NÀÐCÎ8ïüv7ÊÿÐQ—` €`€ €‚` €`€ €‚` ¢\è]/=ûŒ^~öi9ѨN8ýL1ýh. è`èE+Ÿ~JÞúŸ*ˆEÕÔÒªUO=©+¾õ=1m:t-  ­u…Œ±j‰Ç•H$äº ­^µ‚ :… `èE™3Dµ…-Ú¼ÅÕÚõRãn«ÁUÕ\Ð)ÀЋJk4sÚ>µNhÕÌ)V¿xø07ëT. èZ@@/Ú[õE¶4jTÑ&9d“.ûÒ§5¨¢²ÿ}#îfýõ»—ëk¿Y¯„$¹[õ—ëêów®ó¶@ €^T\1JîÉÏꡊ›ôɽiEõèþù¸;õÒ£‹µ|cƒ¬$Ù½ZûôR½¼e¿·©õ-:}ªf]·LMü3 Ë@/ÛÑҢϼý¾­Ö½|ˆüö)WÍ„ šX3Xþ ÐeXzÙÚú½j,Œ)Ò’ÐÞD³šqEøÓsd’>}û#ú4ýt##+ÉH¹;r=æL=}ÀÐ˶·6Ë––h–ÞÔñ±wµ/ÞÚ-ŸÇî^¬«gNÖ¬o.Õ>5kÉWÑèÓ¨Wãþ-KtÍô±ºè®mr½Gh÷o/Õ¸Ã?«‡ëzëîÒ¬é‡ë°Q‡iÂQ³µðúǵµ üÚºúþÙ‡ë¨÷è­VI‰5ºṉ̃:å{/).IîVýù›—謓ŽÖcFkÔ˜É:~î•úåʺô„µ»CÏÝz•Î?qŠÆŒ>Lã&¯Ó/¸L·¿ÏøÞ8t@/{/Þ,•–êÛCžÓå+ÕÐM°ä*O(áZI…šqÊñ*zûE½´Ç‹Xão<¯U{ZõÚÊ—Õ,IjÑËÏ¿,{Ì,Í,s4ô—ê›7߯?ýåºùsc´æç_Ôw×°¢È¶¼©{¾p™îŠü³n¿e&Är´Wëž{N;&_®›ïý­î¿íß5'ò¸¾ýÏßÑ’}’Ô¤—nü¤>qýJ ÿä"Ýõ»ßèWß™-­|B«6¹ß‡8ÓVõ•8èg'Ç¢€žB hèe;Z›¤Ö¸ö9Ūvö«>ïž¡yÕ\Ý´jnjÛžxªfh‘ž}¡Q æë½åËõné ™çŸÕ+­çh¦Y£eËë4éc§h˜c™rš><Å{ìÑÓ¾¡µŸ­ß­zCñyÇ)–ï“&6ëÏ_ùž¾óÎYºå_ÕÌAmOH—N¦_ܾV¾°X7qªb’ìÎwt›y(ç÷À!ÅÉÚvÆdºjjÚ{  zÀÐ˶ïoÔÆ:=ºQ·5@ïÇ›{f >ìC:ã¨ýzvéKj±uzö鵚qÅ•:yÏ2=óVB‰Ë´lÓ81{¬"jц¿]¯Ïž÷A͘2I“»Tw®O¨¹©­¯ÕÕ–ûþEW=T¤ÏÞü}=¬c¿r"£Çj´³G»÷XÅß|I¯6ÖjÖ©Gä›8„p^y7tù ô Ǩ€nC ½¬vë&ý*ú;mk-×Ò÷Æ©¤®±‡~ŒÒì3'kד×ÿÕ=§'^§ÓÏ»P³~GO.}W[ž|\kFž¡³&E•Xû ]vù¯´ù¨+õ³ß>¬?Þý-Íuà™æª™çéŒÚõºãë7jùûkÏl"1Eå*áJ6WBE¢¼r ø»¶Ã‡ñ«]9 5y »@/²Öê°5ÏjnÙfmßV®Ó–¾&ó~}}öˆÆŸ{®&o~TÞög=SýA:f„N›=Y¯-þîýËKª3WÓbRóš—µÖ©O}õcúàÑ“5yÚÑ_‘ý+ĆÖà5*š¼@¿øýuúŽ[ôÏ_¸Oë;¹¼qtìáÙ¬•/l–Ë?”sÍH&¸ß(«ì‚i7tùHÔ…‡¡&ÏÀ“Ñ(ô`èE»víÖ[ïê®§&éþ§GhçÎjhèž `»{±®ž9Y³¾¹Tûü}‘qótþôMºç¦¿jð9s49êhô9çjò‹¿Ð-+Fë¼ó§+&©`â·ÏëÞŸ< 'W½¢W_}MïÖÂ^§RC*­6=ó'-Y_¯` =ì#úÉ/¿¤QÏ^§+þŠš:ó¢ú\}j~•^øñ•ºö¾Çµ|ùúý=¢Õ‰ü߇Ӟ]L´ g¤ä¾Ð7@/Ú¼e‹6ªç6NÔ[Û¼§ä} Ýöù¬U(œUd¬.¼ä$ÅìxÍ;ïHE%9cÎÕyÇ™£>¦‹ŒJ’¢Ó?¯›ÍSäÑkµð¼9š3ÿ‹z°~¢¦ìáѺèË—kÆ{÷êÛ÷®S<ãó–̸J?ûÊT­ûéWuË«(6•:ûû÷èG”èé.×Å¿J·¬¬SÔ8Š8y¾7e&ôÁ¯Â`æ ]5Þ4©î3&c èOÓg]ü9¾Üúÿ·’µÞDºue½r­+ëzÛ®›u]¹®ëL¨~ïN-YüWÈcÕª4gÎ<.Ä’H$ÔÔÔ¤={öè•W^Ò±Çßás<óÌ2-^¼X‘HD[·nSccƒ&Ož¤ý×U4å"·uýßø©æœù;ÍzðI]{\Œ ÒŸ§O?F•••***R$á¢t×J- 54»jŽ[ÅV®ÍÿÖˆä]Í «]õqÅ"FÇV¢’‚ƒà”cg©¼¢ZŽ‘q9ŽãŒÈãÝ6Ž7i`ŒŒq¼l*9‰`Ò³†Ð ð^—…^·y?Äé×o6õúͺ®ÿúÍßç¿f³®«º½;õÄ_þ› €N;}îÇ4¨¢:ýÏq¼×tŽ‘c;ù:Ï^ç™ÀK=^ï@w"]€^‹yOÃÇÓI'¤{î¹GkÖ¬ÕwÜ¡Ë.»ŒApJ\ëþ|§^°4nÄ`EêÖéo?¿]ë»DߟFø Ä]«Ý qÕïw½0¨’O1EQ£‘•15µººåñ÷ä8Ò¥' ÑÐr~¾€¾Ç(Ï‹ô}F&ô|àmQ–€®“¦+€ƒcÎð1m=Ѐ •••ÉZ«AƒëÃ>G…ºûî_kéÒ§T]]­‹.ºˆ‹$I¶^ï®ú«n}h­6ïl[:B“Où¤~ñã/ëøB.m»âÚÛ˜h³Ò·½ŠbŽ.8¶Bk·6éêßnÔ9SëÒ“«yiô5meÀ’÷îk½7yXãêg¿ü< +†£Á÷™ôj#*f`@ =‚zQQQ‘$©¢¢B’4{öi?~¬~ö³›tÿŸÑ+ïЗ¯Áå‡xÊi*uæµéÌkù7$¹VÚ^ת†æD—Ÿ{rM‘ž\­[ŸxOo¼×¬oœ[Ó%m¡tõïGÉØpœªü5F²þþmC €®‡¦ÞaXÂǘԘ4ãpÆ¡ÐØÉ€^T\\¢„u4xðàÔ¾±cÇêŠ/}]å'ëÍMujj‰s¡„¸VÚ¼§¥[Âߤ C µðäj­ÚР¯ü÷»jjµ\x ÏJ®Ÿ–Ü2¡I6㯵Z{ 8¨Ñ§?¶LŽ3ý±g²1tplJò ½ƒzÉßWlÖõ¿Ù ÎiZý^eè¾¥/nSuU™®¿j–†)åbÙ^תæ¸ÛíŸgú¨b?¶Tv5ë{Ù""` ïÈšF L¼…[ñ™ð!† 8ì`Ô„†•&øFÃÀx4sìÉHz0ô’}ûã:rÜ`v\­ÆÔ ÝwöIcõƒ/¢‘ÃʹPBö4Ä»µò7ÓEÇUª¸ÀѪ º{ÙNþ€^br·é…}MpÛ{@ꓪÎàåºj|ê*€³Ç  u¥ ¦Âãºk@/9ÿCcòÞ7zÄ .€,q×jwcÇÂ_Ó¼Sï>¤ØÞŠÚí2J(®Jµ–LSëèó•4¥ÍÇ—8:q|©žX[¯‡_Ü£yGUhHCH /3 8+ë¯l$k pðãÍä˜ÒÞèW³äô ÌÞô»â²¶}˜mQÑ«‹4xÿoT8ºDÑ‘Åwc¿/·ñu5¿ó+Õ5ÍTã´É-©É{®Y˵d]½šãVw=³Sל3‚¿  ¯0’±’MUÛt»v›\ŸÍ?ÐqdŒtæüµ6õGÖ•÷ÔØçŸ%õœb˜ ›ßTÝKçjï´ß(1hrÎóU—GuXU6ìjÑSoÔëŠÓ‡©¤€*B ÷x©¯ü¦wyÉŸuïçÞÛm½©7+ ªꇼ®¬kÓ·Á¯ ûA²µ~qðó§7,I0@ÿAšŒñdphé§¶Æ?0þ¦—ñBßÀmÿþdõofûçŸF¤ÁÐs€à ÐFŽã(‘H(‰pA€Hþ¬°ÖOÇ4¶¸Áº¾6¿x†L|WNaêŸ[/ÛܪAš”óX§0ªÁG;²/~Z{N|L6Z–ó¸‰ÃŠ´aW‹ZVÏ­oÐìɬQô‘‘ˆÿѦ2`˜X³’ŒYëú“{ެ\9’ãzXÇkí‡ÀÆø­dwÞ`Ò|&2$ÀýdؘUûÚo­œ• w3ª~ãïsÒ¾©}É?™Ÿ#÷ú¿€îE 7›Tø[VV¦úú:UTTra€¨¯¯Sii©œ@»(XCsûÖþì~Q…W¤|˜êŸ[¯Hy‘JN×öóYÄѠÛոîf5MýzÎcÆ-Ôß×x·—¯ßG ô©AI¸ t2¡M?»¦»XIŽK޼GyU¾ÖC`¯ýsòQÉÑáóŠ^Ðýh̘kgð̓É×ç©ð7gœøzg‘I½†öÏÐÀApGÑhTC† ÕæÍ›U^>ˆ*`  ‰DB›7oRmí(E£Q9m„Û«9Þ¾”¥pý*š8D’TŽëØqG9Uk×®épœ ‹‹KÔÔÔ¢Õ«Ws{à8cŒjjj4räHž7:qÜ®ššÚŒë¼E[¶¤‹4¯#_[¦1GN’$M¬‘~}«îÛ¤å/o‘µRy±tæ £ž2\¶Õ;ŸÛÒ¢ÖQçh÷Î÷B瓤ÚD6£õIDATÚ¹Ñ*Mæ­|Ìa%íþ>tÃü\VèT pz~Íš@*è <©¿X8=˜]ëËÝúë`2¼™çþPèªø <*Ø:5.Í<¹ÉËzàéþ¬‹?—~[·¿Ö“¬•µ’µ~›µr­+ëW´¹nBÖu½ 7וë&T¿w§–,~€+ ä±jÕ š3gè«W¿ªíÛ··+N†¿Ã† ×Ô©Ó¸xè7ö5»Úö~K»Ž-}îŸ4xÿ29‘tu¯•´§Aji•ªË¥h$ðkÕÐÓÞÙKdcƒržó‘—öêïkê$I_=§F§M.oók˜rì,•WTËq"2ɪoÇñ¶ñn'ÝBÌ8ÞDBF;1&€ü²»$_Ó%êxçm$+|“w¦Ox=˜qÊôMB_€$T§krÜz]8(Tí‹Ã­Ÿ  ·P ô Å‹ÿÌEºIyyù+“áo4ÕÆïhãÆw¸p˜bŸðþd*mã1e’þoEÞ»§Hšr„w{ÿÛ¯kñÛ\f ïñ*ÓëËßöö[yÕÀÊ(öMòî NÊY{GZ÷ È!džpèëï¿éÛ-±ø/ô>` {ìñ\ mݺ%oœ +++³ZèÐe·‚–‚!°äÁ~êküõ~Óͽ6ÐÉ„rœ+¹/S ÐÏÇ‘9÷æ=0³½sv»çàšÁ¹ÏEõ/ô,`À€‘ v3C`Â_À@ÕV,%?øÑn0ö«{Sa°•?Á—>W2Îúœ´‚èïƒÈÜ»sm™ÌûÚ ~óŒ  Ç”Ì8y›ð0På ¥Žàz0`À"øŠòWKùÂàÐ=öÀPvøŽÐXÐû€> aþ´&¡§ßjÒî†8ЧU•FuêÄbÍì(b,胂oí ƒóîægàAvÉØÐ7}À_ÖºZ³=Îô _°’^ÛÖ*)¦ùSx¡ôuí ƒó>š €6ǘ€¾‡è–¾¹_’ôosk4¥¦˜ èÓVoÙ¯,Þª¥oÆ5J èGÚš¨ëx8 €CuìèÛ€> Ùö™ðÐY[¬]ûX²h˜à``p¸00ÀA 0 ÀÀ!àèœÀE8ÀA 0€]sÍ5zñŹÀ!‚`{íµ×ôo|ƒ 8D‚€C0À!„ Ø€~Âî|HŸ_¥)W=¦z5é>W«Â£®ÕKqÿ€æGõÙÅš}Ëf¹Þ#´óÎy*|1… ˜€~ÃU<Wܵ’Š4óô“Uôæ =¿ËJ’âk—iù®½üÜJ5I’šµrÙ ²˜Í¥äE ,Q.Ð?˜ê‹t÷Æ‹RÛöÔ35Sÿ¦'ŸmÐg/(ÑÖ§žÒ;å2ËžÔ‹­óuŠyEKž~_Sž®–ïâÀ!ê‘GÑ®]»´{÷níÞ½[[·nMmoÛ¶-u;O:U ,ÐŒ3¸x@?D0ÐO™gëÜcµô±çÕl÷jéã¯jæ5_×i»—è‰u %Þ^¢%&jΜ‰\,€ZZZÔÜÜ,IjmmUKKKj?€ƒ ` ¿rÆèÃs§éÿÝùW­Ú[§¿=;Qç,ú„Ê—|O÷?öŽÄë•ÃÎÕO§Fõ0W YóçÏo×qTþ0ÐoEtÄ…Ñ´ïÜ¥ûÿs›žv¦®™0RƒæL××ÿx‡~y^£/øfĸR€ü~€…Ð@?aw>¤ÏŒ¯Ò”«S½¿/røGuÉŒ ºíúU9ÿM‹:;ÿBM[q£nxf¬>zɱ"ÿä2uêT-Z´H7Þx#á/0€P ô#ÖZÙàŽÈ}âÓÒu/l×G/>Úûÿ]|ü¿ieüSZx?â€0*~€tè'Lõ…ºóí 3ö:yÙ£ÚwYp×8]½´IWsÉ¿À¡¡S°‘‘•d¤p5"ú”éÓ§káÂ…š>}z¯} &0†н¨À~ô£q€CˆÓ¡£M[UTt}ÕËÏ/ç"z˜éä˜ÀÁh;nçäœa#2–ºÌ+€8Ç|:9V$ûºVž¸½Õ'2 VŽØäÜe‚û²Ê5˜®@Þ±¡1 'Mž$£Jà`äomÚ³‹ :H;Þ8ȰèN‡aBüê f쀃QUUUiT¯mÙÏÅôy¯mÙ¯!eÞï®¶Ç&ÕEÆdŒ%t6gíŒ$ëÿ7ù!u12ÆÈÉXáÔ‰Åzm[«®_¼U»öŹ €>mHYT•%QM¯µ1ô£_#“|Ã`Æ’"þ F‘@× T·5õf·2[ø%'ó˜º:kîdGÓFÄd-×Ð÷Y+M¯‰éÜIÎÆ&ÏØ±cãOí®Uùæ`Œd­WÀaw¨Ÿý2Et^ÄX7Åè¼)%\ @?’àhãDï¦I7Œ9Pç–@§å.ÛÈ親5L ñ5Ê&êO&ÇF~Ðá±eúpÆ“@pÚ¾ÛŸ  Vn¤wËøë{k¼1c€ô¸QþXѤ“©ÆÐÁ1&É/Ðu²à¬é·À„]¸…Ÿ b˜¸@z€šàãÊÌ1$#Jàà8&gp›nól‚ÛR`’.]ÑaŒÃ•@xDiœ@pöXR¡î2ÁT8x"a #:”Üšäÿr­ãF €ä¸19647šÔh’ t“hÞ{Œd¬dS•ÀV6yŸM®ëæè82F:sþ?ÈZ›ú#ëÊZï©}þY¼Û©Íô~þNz•ɼ•*ÞÍîãÝ–¨öMýqû2Ú?§— V8X°—úzÁoz—·‘œ•³Þšn’¬¬7eg¥A•Cýוumúv øõBa?H¶Ö‹”C‰ozÃ’ô¨p·e"ú©­ñL‡¿é%A¼Ð7p;Ð9ÆûnÿœãÓˆ488ù+€o26 9+ÉÈ‘µ®?)èÈÊ•‘#9®w€u$keüØÿ£•¬ñÎLzƒ™¯!èY&«ö7´ßZ9+îfTýÇßç¤+|Sû’2?Gîõt\´Í{3Ú@'ÚôôœMMZIŽ{K [¿m´õÂÞTìµN>*Ù":|^Ñ  §™Ü;ƒoL¶N…¿9ƒàŒÀ×;‹L*è5´ºITò&ðRkòfµoÌfMh¾.Y,c%×õv%ë§ÇþIŒI·6~©¯ƒ‰@€^aŒÉµ3‡[By¯ä­ù«ìª`ùm èÙî9Wûçœ_€6µ] 3ª€ýù8/ô ?Ä+vüöÎN ê7]ñ›lb%æùú“¹iR­I}̨α6pæþÜÕ¿ €®’'ÎWld“9°Í#k]:/YñkÓZ@§æ“‰r I¦û3@ï097rTå&+‚3B_ùÁÁû²Ö „É©ÿ]"‡Û@Ãßdå®É “¥¿Fެ¬¿i¼vÏÉ5­d•¼mÓA¯Ï€>ÀCÚäíÔ;ùÒ•½~[èÌ7;üU …tþ‘ퟀÎi»´Qºýsò¿ÆÈX›'Nò¯á³Â}£m`=àÀúÁY ©è &»÷söÁê]þ˜ªú µŒ6áŠáÌÏFÖ t™6àtp› ~½Â^›KkûÇ&[@c¼3%[@Û>K:à<#zW2ßUF èÔíìªàTPœþšŒ ˜ ÐUBpvèT pjSÖBàPAoð àIýÍÀ:ÀéÄìZ_Kõ/@¯2ˆfsµ‚Uül_fž<ãóÑþè´¬ à|!pª´ñj}½ð×[X¯Ózmæ'H d\l³ï@¯ Õéš÷…ÚA Uû†ÃâpëgÂ_ +EÛwXF,ùÛÞ~빂`3c=9«`ïhCË?€¾Ëä݇¾þŽpð›¾Ø‹ÿÝ#gœ],C`)»8'MÕõz-£³Î,öÑý oÈ]ˆkò˜ÙÞ9»ÝspÍàÜç¢ú8xy+€Û ¥ä?Ú Á~uo* ¶ò'ÓçJÃYŸS$À}Bž06çêÀ&ó¾¶‚ß<ã@Â_ K´Ù:o,å ‚GÙàá6u§Užf© }‚iÏî\k›\›yOHø t®œ;–RmœÃy°Âap(-ýfY O2íÜm²w›Ÿ‡ðèZÑö”œ˜Ë[ ,àP,š?ç/ýåo Ï2í¿Ç´óq¿@·ˆväàüÕÀR¾08t=ÀãÐ?˜ßSèÑŽ> 8a×®08ïnËÕèW:Üú=#z0n_œ÷Ñ\}€ŒÐèyÑ®:Q[|‡Ðò}K´'> ƒÐýþ? ¥á©W¯ãYIEND®B`‚GSConnect-gnome-shell-extension-gsconnect-43258f9/data/metainfo/meson.build000066400000000000000000000010601460766671100267160ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # AppStream MetaInfo metainfo_file = i18n.merge_file( input: app_id + '.metainfo.xml.in', output: app_id + '.metainfo.xml', po_dir: '../../po', install: true, install_dir: join_paths(datadir, 'metainfo'), ) appstream = find_program('appstreamcli', required: false) if appstream.found() test('Validate metainfo file', appstream, args: ['validate', '--no-net', '--pedantic', metainfo_file], suite: 'data', ) endif org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in000066400000000000000000000135351460766671100363600ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/metainfo org.gnome.Shell.Extensions.GSConnect GSConnect KDE Connect implementation for GNOME CC0-1.0 GPL-2.0-or-later https://github.com/GSConnect/gnome-shell-extension-gsconnect/ https://github.com/GSConnect/gnome-shell-extension-gsconnect/issues/new https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/Help https://crwd.in/gsconnect GSConnect Team org.gnome.Shell.Extensions.GSConnect.desktop org.gnome.Shell.Extensions.GSConnect.Preferences.desktop org.gnome.Shell.Extensions.GSConnect org.gnome.Shell

GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows.

With GSConnect you can securely connect to mobile devices and other desktops to:

  • Share files, links and text
  • Send and receive messages
  • Sync clipboard content
  • Sync contacts
  • Sync notifications
  • Control media players
  • Control system volume
  • Execute predefined commands
  • And more…
https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v57 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v56 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v55 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v54 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v53 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v51 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v50 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v49 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v48 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v47 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v46 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v45 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v44 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v43 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v42 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v41 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v40 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v39 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v38 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v37 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v36 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v35 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v34 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v33 https://raw.githubusercontent.com/GSConnect/gnome-shell-extension-gsconnect/master/data/metainfo/image-01.png GSConnect in GNOME Shell
org.gnome.Shell.Extensions.GSConnect.Preferences.desktop.in000066400000000000000000000005361460766671100360630ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later [Desktop Entry] Type=Application Name=GSConnect Preferences Exec=gapplication action org.gnome.Shell.Extensions.GSConnect preferences Terminal=false NoDisplay=true Icon=org.gnome.Shell.Extensions.GSConnect Categories=Network; org.gnome.Shell.Extensions.GSConnect.desktop.in000066400000000000000000000006611460766671100336220ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later [Desktop Entry] Type=Application Name=GSConnect Exec=gapplication launch org.gnome.Shell.Extensions.GSConnect %U Terminal=false NoDisplay=true Icon=org.gnome.Shell.Extensions.GSConnect Categories=Network; MimeType=x-scheme-handler/sms;x-scheme-handler/tel; DBusActivatable=true X-GNOME-UsesNotifications=true org.gnome.Shell.Extensions.GSConnect.gresource.xml000066400000000000000000000117361460766671100343460ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data application.css ui/connect-dialog.ui ui/contacts-address-row.ui ui/contact-chooser.ui ui/legacy-messaging-dialog.ui ui/messaging-conversation-message.ui ui/messaging-conversation-summary.ui ui/messaging-conversation.ui ui/messaging-window.ui ui/mousepad-input-dialog.ui ui/notification-reply-dialog.ui ui/preferences-command-editor.ui ui/preferences-device-panel.ui ui/preferences-section-row.ui ui/preferences-shortcut-editor.ui ui/preferences-window.ui ui/service-device-chooser.ui ui/service-error-dialog.ui org.gnome.Shell.Extensions.GSConnect.desktop org.gnome.Shell.Extensions.GSConnect.Preferences.desktop org.gnome.Shell.Extensions.GSConnect.service.in org.gnome.Shell.Extensions.GSConnect.xml org.gnome.Shell.Extensions.GSConnect.sdp.xml icons/org.gnome.Shell.Extensions.GSConnect.svg icons/org.gnome.Shell.Extensions.GSConnect-symbolic.svg icons/computer-symbolic.svg icons/smartphone-symbolic.svg icons/tv-symbolic.svg icons/laptop-symbolic.svg icons/tablet-symbolic.svg icons/phonelink-symbolic.svg icons/phonelink-delete-symbolic.svg icons/phonelink-lock-symbolic.svg icons/phonelink-off-symbolic.svg icons/phonelink-ring-symbolic.svg icons/phonelink-setup-symbolic.svg icons/group-avatar-symbolic.svg icons/sms-send.svg icons/sms-symbolic.svg images/enter-keyboard-shortcut.svg icons/mouse-left-button-symbolic.svg icons/mouse-right-button-symbolic.svg icons/mouse-with-smaller-scrollwheel-symbolic.svg webextension/org.gnome.shell.extensions.gsconnect.json.google.in webextension/org.gnome.shell.extensions.gsconnect.json.mozilla.in images/chrome-badge.png images/firefox-badge.png org.gnome.Shell.Extensions.GSConnect.gschema.xml000066400000000000000000000137411460766671100337550ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data true false true true "" "" [] false true (0, 0) false "" {} ["sms", "ring", "mount", "commands", "share", "keyboard"] "" false "smartphone" [] [] [] [] "" false true false Enables custom battery notification 80 false false false true true true true true '{}' , 'restart': <{'name': 'Restart', 'command': 'systemctl reboot'}>, 'logout': <{'name': 'Log Out', 'command': 'gnome-session-quit --logout --no-prompt'}>, 'poweroff': <{'name': 'Power Off', 'command': 'systemctl poweroff'}>, 'suspend': <{'name': 'Suspend', 'command': 'systemctl suspend'}>}]]> true true "" false true "lower" false "mute" true true GSConnect-gnome-shell-extension-gsconnect-43258f9/data/org.gnome.Shell.Extensions.GSConnect.sdp.xml000066400000000000000000000023661460766671100332140ustar00rootroot00000000000000 org.gnome.Shell.Extensions.GSConnect.service.in000066400000000000000000000003361460766671100336100ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later [D-BUS Service] Name=org.gnome.Shell.Extensions.GSConnect Exec=@PACKAGE_DATADIR@/service/daemon.js GSConnect-gnome-shell-extension-gsconnect-43258f9/data/org.gnome.Shell.Extensions.GSConnect.xml000066400000000000000000000054171460766671100324270ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/000077500000000000000000000000001460766671100233725ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/connect-dialog.ui000066400000000000000000000122611460766671100266210ustar00rootroot00000000000000 1716 1764 1 1 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/contact-chooser.ui000066400000000000000000000121521460766671100270250ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/contacts-address-row.ui000066400000000000000000000065011460766671100300010ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/legacy-messaging-dialog.ui000066400000000000000000000212611460766671100304070ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/messaging-conversation-message.ui000066400000000000000000000062301460766671100320410ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/messaging-conversation-summary.ui000066400000000000000000000063531460766671100321200ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/messaging-conversation.ui000066400000000000000000000116741460766671100304270ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/messaging-window.ui000066400000000000000000000332011460766671100272120ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/mousepad-input-dialog.ui000066400000000000000000000267461460766671100301570ustar00rootroot00000000000000 touchpad-eventbox touchpad-eventbox 0 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/notification-reply-dialog.ui000066400000000000000000000171301460766671100310070ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/preferences-command-editor.ui000066400000000000000000000170261460766671100311400ustar00rootroot00000000000000 application/x-executable False True dialog command-filter True Choose an executable GSConnectPreferencesCommandEditor False Cancel True True Open False True chooser-cancel-button chooser-open-button GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/preferences-device-panel.ui000066400000000000000000004723321460766671100305770ustar00rootroot00000000000000 80 99 01 1 5
Encryption Info settings.encryption-info Pair settings.pair action-disabled Unpair settings.unpair action-disabled
To Device edit-paste-symbolic settings.send-content From Device edit-copy-symbolic settings.receive-content
Nothing audio-volume-medium-symbolic settings.ringing-volume nothing Restore audio-volume-medium-symbolic settings.ringing-volume restore Lower audio-volume-low-symbolic settings.ringing-volume lower Mute audio-volume-muted-symbolic settings.ringing-volume mute
Nothing audio-volume-medium-symbolic settings.talking-volume nothing Restore audio-volume-medium-symbolic settings.talking-volume restore Lower audio-volume-low-symbolic settings.talking-volume lower Mute audio-volume-muted-symbolic settings.talking-volume mute
GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/preferences-section-row.ui000066400000000000000000000060661460766671100305110ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/preferences-shortcut-editor.ui000066400000000000000000000135501460766671100313730ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/preferences-window.ui000066400000000000000000001324461460766671100275510ustar00rootroot00000000000000 False True 10 6 6 True False start Device Name rename-entry 0 0 2 True True True center center True name 20 0 1 _Rename True True False False False True 1 1
Connect to… win.connect
Display Mode Panel win.display-mode panel User Menu win.display-mode user-menu
Generate Support Log win.support-log Help win.help About GSConnect win.about
GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/service-device-chooser.ui000066400000000000000000000136671460766671100303030ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-43258f9/data/ui/service-error-dialog.ui000066400000000000000000000200401460766671100277510ustar00rootroot00000000000000 expander 0 True GSConnect-gnome-shell-extension-gsconnect-43258f9/data/webextension/000077500000000000000000000000001460766671100254675ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/webextension/meson.build000066400000000000000000000026511460766671100276350ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # WebExtension Manifests if get_option('webextension') nmh_manifest = 'org.gnome.shell.extensions.gsconnect.json' # Google (Chrome/Chromium) google_nmh = configure_file( input: 'org.gnome.shell.extensions.gsconnect.json.google.in', output: 'org.gnome.shell.extensions.gsconnect.json.google', configuration: extconfig ) chrome_nmhdir = get_option('chrome_nmhdir') if chrome_nmhdir == '' chrome_nmhdir = join_paths(sysconfdir, 'opt', 'chrome', 'native-messaging-hosts') endif install_data( google_nmh, rename: join_paths(chrome_nmhdir, nmh_manifest) ) chromium_nmhdir = get_option('chromium_nmhdir') if chromium_nmhdir == '' chromium_nmhdir = join_paths(sysconfdir, 'chromium', 'native-messaging-hosts') endif install_data( google_nmh, rename: join_paths(chromium_nmhdir, nmh_manifest) ) # Mozilla mozilla_nmh = configure_file( input: 'org.gnome.shell.extensions.gsconnect.json.mozilla.in', output: 'org.gnome.shell.extensions.gsconnect.json.mozilla', configuration: extconfig ) mozilla_nmhdir = get_option('mozilla_nmhdir') if mozilla_nmhdir == '' mozilla_nmhdir = join_paths(libdir, 'mozilla', 'native-messaging-hosts') endif install_data( mozilla_nmh, rename: join_paths(mozilla_nmhdir, nmh_manifest) ) endif org.gnome.shell.extensions.gsconnect.json.google.in000066400000000000000000000004461460766671100373500ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/webextension{ "name": "org.gnome.shell.extensions.gsconnect", "description": "Native messaging host for GSConnect WebExtension", "path": "@PACKAGE_DATADIR@/service/nativeMessagingHost.js", "type": "stdio", "allowed_origins": [ "chrome-extension://jfnifeihccihocjbfcfhicmmgpjicaec/" ] } org.gnome.shell.extensions.gsconnect.json.google.in.license000066400000000000000000000001651460766671100407670ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/webextensionSPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later org.gnome.shell.extensions.gsconnect.json.mozilla.in000066400000000000000000000004231460766671100375360ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/webextension{ "name": "org.gnome.shell.extensions.gsconnect", "description": "Native messaging host for GSConnect WebExtension", "path": "@PACKAGE_DATADIR@/service/nativeMessagingHost.js", "type": "stdio", "allowed_extensions": [ "gsconnect@andyholmes.github.io" ] } org.gnome.shell.extensions.gsconnect.json.mozilla.in.license000066400000000000000000000001651460766671100411620ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/data/webextensionSPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/000077500000000000000000000000001460766671100251635ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/.eslintrc.json000066400000000000000000000037171460766671100277670ustar00rootroot00000000000000{ "env": { "jasmine": true }, "rules": { "no-restricted-globals": [ "error", { "name": "fdescribe", "message": "Do not commit fdescribe(). Use describe() instead." }, { "name": "fit", "message": "Do not commit fit(). Use it() instead." } ], "no-restricted-syntax": [ "error", { "selector": "CallExpression[callee.name=\"it\"] > ArrowFunctionExpression", "message": "Arrow functions can mess up some Jasmine APIs. Use function () instead" }, { "selector": "CallExpression[callee.name=\"describe\"] > ArrowFunctionExpression", "message": "Arrow functions can mess up some Jasmine APIs. Use function () instead" }, { "selector": "CallExpression[callee.name=\"beforeEach\"] > ArrowFunctionExpression", "message": "Arrow functions can mess up some Jasmine APIs. Use function () instead" }, { "selector": "CallExpression[callee.name=\"afterEach\"] > ArrowFunctionExpression", "message": "Arrow functions can mess up some Jasmine APIs. Use function () instead" }, { "selector": "CallExpression[callee.name=\"beforeAll\"] > ArrowFunctionExpression", "message": "Arrow functions can mess up some Jasmine APIs. Use function () instead" }, { "selector": "CallExpression[callee.name=\"afterAll\"] > ArrowFunctionExpression", "message": "Arrow functions can mess up some Jasmine APIs. Use function () instead" } ] }, "globals": { "clearInterval": "writable", "clearTimeout": "writable", "setInterval": "writable", "setTimeout": "writable" } } GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/.eslintrc.json.license000066400000000000000000000001651460766671100314020ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/config.js.in000066400000000000000000000002741460766671100273760ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later export { default } from 'file://@PACKAGE_DATADIR@/config.js'; GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/data/000077500000000000000000000000001460766671100260745ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/data/album.png000066400000000000000000000015621460766671100277060ustar00rootroot00000000000000‰PNG  IHDR{C­ pHYs.#.#x¥?vtIMEä PÕKÀIDATxÚíÁàùS_áUÀo´ÿØPIEND®B`‚GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/data/local-certificate.pem000066400000000000000000000037411460766671100321560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFpTCCA42gAwIBAgIUYVlJk/6vssQfUOXo9SNb7NfxixwwDQYJKoZIhvcNAQEL BQAwYjEdMBsGA1UECgwUYW5keWhvbG1lcy5naXRodWIuaW8xEjAQBgNVBAsMCUdT Q29ubmVjdDEtMCsGA1UEAwwkZDJkMjExN2EtNDlmNy00NzllLThkYWYtNjhjODlh M2Y3YWQyMB4XDTIwMDgyNTIyMDEwOVoXDTMwMDgyMzIyMDEwOVowYjEdMBsGA1UE CgwUYW5keWhvbG1lcy5naXRodWIuaW8xEjAQBgNVBAsMCUdTQ29ubmVjdDEtMCsG A1UEAwwkZDJkMjExN2EtNDlmNy00NzllLThkYWYtNjhjODlhM2Y3YWQyMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxsFITnzdsa4lhk8opxJ8lcvEWZvn WwhjJIiu6qFnzlg9VAnS0QrUUMxNxOVVhmeSSPlV3+r23h3y4I/Irzm8PBOcBM4A uvYZneP9+K/24edrCsdoAIcws5nI2JTvbqF5XowRKy4YX9EaIQS3EvYfKrjYDiiF 0rVmV57GotrC/ntElF5CZlKO2338FuC78lWhnKHd6vJ6OyciMvdz10BsfkLBd0ME 7v74n1P0w6jV40J1KORtdtXKRo2XHgDBiDM557jHJTiIFNhb5LLOfcinCXgSuVXf otfwsBuD1evRrnA3hY/k/LHYUNEE0HscQXmhrPA7BdrSB4Cc5lPLVyTQa3rCD6vQ j/RL6dAwNeiiW8lgC+l4x8QBvxks8JLpLzLO6LwBf0QoOBQOlksgFI4qa+vku39k GLHihWK7T0eNW5ly1v3siQfcX1Y2BSJ+mywS7rqWLjAFbBLmwjKErYIoOpOC316c 9Z7YhQ8r8CBf1f91h5uyxIZ01NWyqn91ncJ5yn9ivXBEdF/w0YAYvh9LU18sUYYK AJR4RmE7uFGBmXO638MCXxItxyX2kvAVjJ3zxQbuMnBKVYDh/9vywwpgfIx96e3A PIZMXQPDuFX362FwXRKsf5hWOZMA7Si9CTLRxz05z+OedMR0IpAQLOdAGm9ll3YD HYuuUOFDyiiYP5cCAwEAAaNTMFEwHQYDVR0OBBYEFMjbPPr7CRy6OYk9H/G8BAHj TU94MB8GA1UdIwQYMBaAFMjbPPr7CRy6OYk9H/G8BAHjTU94MA8GA1UdEwEB/wQF MAMBAf8wDQYJKoZIhvcNAQELBQADggIBALXCEPO2z4TghwbDkxK0YQiqokKDs1I/ o6j5HKYw4OhL2WMQPkT0r39bFLazW1EO2mrH9twRSJ9im0Hwrggmw91CNMdy9fuH uNCExw9j+lbJeyBEAZTk/SwruKArEdksEUF7Gpe8eBnJ8epJWfiN95pNkMCxd3Oj mQ7UhALjuDwBAiOrQ9/iQPNU0tubbgfSLC500ycaE8SmEpdMF6bNzURYZLsMaBdi j9/pTezujrch66KVEIjsCHXSguxBR4imZC3nK3x70ILHqd+bOFJNVn1xNfc8fpxy ftRhZphBNb/hN7dlDG2ZkFAXfRu7F2xGmuC3n4OHKOSAEOP3EmYP7YS77+K4bk3s njbjnwZWE9wQPfbuK7fN5Sf8mKUFVZhkPKT22ARvhmKpCjcEnwxLgWxAfpEX60vr 41oZwnXbZwBKzSWhtzo8jQA4otEBS7qO4THi6iUWRcj4Cz4ThzeV6RgWSQemUU0v v8PR/ob84uisQN0nwPRPgHmwSRIb3TgguibKlOCEhNMRwmlPtQZp33AOLL8oPeP3 YTkVajMbUpTvUogh8Cf0xhJ0mHicxgkPbo/tvnnVxkjyCUmyX/juEaQTEbiSk+ww N7W1qp2gz7Sfjr1z4N4QQybEt9yknMoKL1wL1uKSdH/oQcGr9vaZUtJFcSVGlMX5 qCyBnxGQDn60 -----END CERTIFICATE----- GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/data/local-private.pem000066400000000000000000000063101460766671100313410ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDGwUhOfN2xriWG TyinEnyVy8RZm+dbCGMkiK7qoWfOWD1UCdLRCtRQzE3E5VWGZ5JI+VXf6vbeHfLg j8ivObw8E5wEzgC69hmd4/34r/bh52sKx2gAhzCzmcjYlO9uoXlejBErLhhf0Roh BLcS9h8quNgOKIXStWZXnsai2sL+e0SUXkJmUo7bffwW4LvyVaGcod3q8no7JyIy 93PXQGx+QsF3QwTu/vifU/TDqNXjQnUo5G121cpGjZceAMGIMznnuMclOIgU2Fvk ss59yKcJeBK5Vd+i1/CwG4PV69GucDeFj+T8sdhQ0QTQexxBeaGs8DsF2tIHgJzm U8tXJNBresIPq9CP9Evp0DA16KJbyWAL6XjHxAG/GSzwkukvMs7ovAF/RCg4FA6W SyAUjipr6+S7f2QYseKFYrtPR41bmXLW/eyJB9xfVjYFIn6bLBLuupYuMAVsEubC MoStgig6k4LfXpz1ntiFDyvwIF/V/3WHm7LEhnTU1bKqf3WdwnnKf2K9cER0X/DR gBi+H0tTXyxRhgoAlHhGYTu4UYGZc7rfwwJfEi3HJfaS8BWMnfPFBu4ycEpVgOH/ 2/LDCmB8jH3p7cA8hkxdA8O4VffrYXBdEqx/mFY5kwDtKL0JMtHHPTnP4550xHQi kBAs50Aab2WXdgMdi65Q4UPKKJg/lwIDAQABAoICAQCwa0f/Qx6VZHqyaPkws1wa qrAyygvl5d/6wchhQ7uckP5+5elW3EHxJiexqc7samqSk58CDtHp/rNjWL1Nq/XF bbKDIUfMrD24xHLel3KQupVtD+rk7RrxkIOSm0Cb9oCAx9tFdLj18+k5fbHzBrxL c59zkcyXZ6TcCXdPftauhEQvXiuaH5XmhkGJHRo21IOLQLJ2pZyRfP8CNluAqRKk UCTh838hlPiilCcitW6FNqxAC+KOJN5TGcMVQp6GgtHXOVCrXS6NMi7/JSfcxope AVK9Z9gF958Q8ptm+tc3+yuNRlh/ZG0Z7y5Sz7QY+hnkI6iAXecn+aVLXP2U8Hx+ GSc4iJsBzqixYCNBrtkfEBXeL6AR+9z6aLJsYVlxkI7Zbw1uV/wPI5YDyqG3pBLZ fdiqQNd99K6jg/nJdigCXY5KMBAmtj8Cm/B9hsrrhOqBCNEcQ7ES9mprM3YF+euW fNVMUXSnwpH6mFN6KiJnxUJdgzqF3OJiGPAu9JSVZHxH3IZ4gVvjLh5Zg/zKO48v pNvvNLLDg/kU4OZmq9NojRmUhhEITodtky1pysroD+5jJyg4CuE5ewl9eQvSY0o9 0TrA2dbFzrYjT7jW7CEZLZlTd0Sh48qS7KXo19rFSllBWRU686tkukb5JJAuSZf3 2FI/ZJGlNNIuMFQeaq/gIQKCAQEA8am/oZlAp+3ySPlkNwDZUvfbRJ2sAy/aK1IP 4o/368YpXiMK0wSY1o4jUZGRX0c6Y65b+3AJdJ/5hr8lxKq7Yjek1F20wdXo6qI9 wnWQQvvEw2PiYvXj2nk05+deOotDZQgfPAY9FiJG5ohlhePlM0Ndv2cDl1rZLG8X l8q5fHJTS1TGIIXQR/j3qitiRLBMwn4rOvS/JyNuCTJc3DLHETsiLB13wUWGqy6B 42vUlKi9vDRawkpDLyatLEMEKFhNadL/2eg8KB9je1vyz+W9YMxZJujamwfoze6f i8Br46RqI6xof68MagIHcSP2Bb44G5dAlOlH44TvDs036jzzIwKCAQEA0oveZVlB WXPh6He/mzpCgXChI1LELcBsD0wGXDdDu0hJFX2UXhUIdGr+bDc+mBbndKniI/To 5jXsk32AoO1Tfr/M3U8lpDqQSJqnteaQ2iv/TIs4t0MmYqbBJDwQhoBt6WgIk6p7 zqQNxpBF1tMaJgO29XxQwLvz1LKzraZycrK5MX3qmlgndK0gv2uh+41Vj6fq9+9/ CwQmOnL6GqA3XuqMWwCDHzKWuQMw0mlilDj4zEBZrnzSfV/ABlcZfUURY4O0Eamj A0Yw3k6j99tFMPwqkw/7xS+EFf0aRnPIFF9Ad5N6C6nfyDgbUQPTNH7VGzQIcheO Vh3tTAlnNaaS/QKCAQBG/Jj47B6M9Z1tCC0C5zHvaDU1k6c6jGzmusVFxQqLbHss VtjQIZKPu9LuG/d66F5jd403b7KnWnKevTln6sr+T+AQLbJyGdbATYYcwBHvSyuC Ra3zac1TmLUMxe7s/Yl/fQJHzIFXJhxzjW9dBBOImmpIVgc9B4exwLRKd1dDEgYb o7xLQ2NqMNz3VKUaDjuOCifCurAH3CVveCbE2/mTuy4PjVxnHngvgorO9hbM0EBj r3FVjyDrEc5eqRTokP+0bTGQneJF2uqLCvhpT0/wxjYN8up8Dbe5/jVJhO1sQhiX gAZ2M0JPRWdQOcMD7ttmZ2imFVxzndHnJCsfmGXHAoIBAEAD0syRxLLD7w3VSuaR YiMk8Xlh8s/OT4yfGtfy3Z8VrVLhabjpQDbVSSHx8hAf9qOb+2vfTOihwJpfcDp9 rgM9obYwGEvEmpXYn+FIhwYulmLZeZcOzZ71AIhZ0tRyO/jZbrInBZmge6fBudpF ORAR1RDyiULwYoRrCQJlNyr0eCY6GJhw8R4ifXB18zwejsMs1N4pbUEWM+FVkAGE cRFk0uPgVf2oTfdWpwNyk0xpvgusDRhmT0FbWXEUDmXuGAlfw+IS58NZFgahdm0n t/Pa1776/xvHBKwC1nhRP6YiB+HTbyoYrjecB4IsXYz6eyTYPzEhRF+encWenkjL qqkCggEBAMhz/VK+dTBoawGF5gHd7ppBmpuyvxTaFjy9ZkTq6FC5G4yZtSsOhtOQ ve1EF+7hZMkdUIV7B6EzTs6bXPgLaLHbpJym1gTdTGUFNWQvdcF38oSfqEmfBnze CbdbMtCY8+Izx1l6gfduz0J4yBQ0ME2uo8FMOjcWFQuqRRkDIE+UF+LFJuINRqT+ N7RMZH+UYT/a6H+jolqaD+N/qUZzz3fU+GGTwJUpoI+dIUL5kUcGjIg972/XoPRX 4gFWdEG7qKGRPd+IARhXNxfkJwHv8S89RfEOZL2f6LI3mOqHlwda14WTmH6ct9S6 258ZGWdYdv8bBRaaLpN7nTcu8+o411E= -----END PRIVATE KEY----- GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/data/remote-certificate.pem000066400000000000000000000037411460766671100323570ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFpTCCA42gAwIBAgIUOMN5eqYOJUP5/eKfKD2xrUkaBFowDQYJKoZIhvcNAQEL BQAwYjEdMBsGA1UECgwUYW5keWhvbG1lcy5naXRodWIuaW8xEjAQBgNVBAsMCUdT Q29ubmVjdDEtMCsGA1UEAwwkNTBkMmFhZGYtNDg5Zi00YTQwLWI5ZWUtMTFjYmQ0 YWJkZDkyMB4XDTIwMDgyNTIyMDY1N1oXDTMwMDgyMzIyMDY1N1owYjEdMBsGA1UE CgwUYW5keWhvbG1lcy5naXRodWIuaW8xEjAQBgNVBAsMCUdTQ29ubmVjdDEtMCsG A1UEAwwkNTBkMmFhZGYtNDg5Zi00YTQwLWI5ZWUtMTFjYmQ0YWJkZDkyMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwma5h3ijVffMtvjCGQpaPsgpYpDg MBxH+gmSJrHCGLFl6oAOUWLCZL/b870/zhFolZ0ncGuyQRH/nnapbpMAzBFjO/B3 hmSa9hEEIqhpDYDMdAr7lJDP+DsoO1KB5NuawJhQDdtAdF7Gpb/GaQV8FFi7BKRo b9NfQVoMJ0h/TrgtaPaGgmLZj3rfHqtfuf7W6g0qy5gBM1Ty/FJ6ih4dK/SMOQj9 FbI6jdQXbwMiLLRiaUozXMNMoZiYE12MUoCUQDyeQAj3afqjPX4eqJ3zcTZyCtha Qy+1goVjCDLcm1VGKJWJEZ4P5Vii1+U74KfoRgrvXK8xCaVhewWSxzO5L4sPPPCO 4MEO4JerDqiIjEm2ft4JNtwKjaY9kOhc5Oymg/R9ylWbjizEgZdECXnqrjeqVmIm xOm++5Ov4ovl6gRjgwarZjd2EvK66tKuZiMNRRM++gKsIsNdzv87fwPforSTs7jG V9tdN4f0HAbDGCUs5VtDym+VBfEPiO6ujHM3w42yOPI7sMKOpkOA8BbrMzOp4zPa qBdssQvqW7T0yWqPHBB92NFBI61j4NBS5P5QWcPoOAPTlopvoASdv7Ol5gnzi8o2 mNbc6Z3Prr7PlzRcYSLEqqduTwjL+BAH83B47xH81GUL0ORO3SpKp1iA13y4nzFC JUu+lVaVVKgfuKkCAwEAAaNTMFEwHQYDVR0OBBYEFP0VzjfOBxXa98JtmYAVD3H/ 1PMpMB8GA1UdIwQYMBaAFP0VzjfOBxXa98JtmYAVD3H/1PMpMA8GA1UdEwEB/wQF MAMBAf8wDQYJKoZIhvcNAQELBQADggIBABCXawCETkwtTMMcOtw1RjxqLupv9tmZ A6Y8dRtwaalIFxnwmki7nNbWjI1xIvkGA/gFmNUsKcCyCVaQxgTevmlOaSj0/v0/ kUmyOVbhb+HUVC4swBL5R+EirBREwpZF712KZdirdeqemtO1l2lmqhLYo3dHnbiX ZJguVN73ccAT2B+v+q724BLN25ztVXDLAT043uOCDr4uRKtuJ7pOhEfyjJOJa4J1 nScl07UVoC7WxMeeMtErtbZmY7FOhmjVGs/a+uP+7LME5APE3h3oYBz9LgwUvNaD 7HsAlHO3aTuRFE7T0AHmFghBsL7sfNqyEeveuvmZNOUW9Xj8CRZ94cvn7pucElEs 9uTbr6q5WWIXzIOJAiXMevtXCuNixw2S1U2prahJ5vUU5iSiWynFsI7+w8aLr7Oi 2mdqKd4a7QF0kwMhqxWsdbTWvhLugOX6MjYyZ9iXwg0LFOS63wqkMrIQgcf+rPXL jQ38b+xdl/OMDvfozeOnKXAxWFwLiHqFcIGrp1XJKYEYbDE3PK3sS7tPZ1goM+1S tarfmqhtMZ5FLAjPP7QJ0v82b2Y+PFRkbkFqn3HAbCcjgBEODTmH4W+VoqQAViW6 Ghu8N3E0hCigwybhy8/nM4kXPI98ROoQGXpOJnyeTAODXcAvv2ecmunh6J/NqcI7 cmX79mp8S+vH -----END CERTIFICATE----- GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/data/remote-private.pem000066400000000000000000000063101460766671100315420ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDCZrmHeKNV98y2 +MIZClo+yClikOAwHEf6CZImscIYsWXqgA5RYsJkv9vzvT/OEWiVnSdwa7JBEf+e dqlukwDMEWM78HeGZJr2EQQiqGkNgMx0CvuUkM/4Oyg7UoHk25rAmFAN20B0Xsal v8ZpBXwUWLsEpGhv019BWgwnSH9OuC1o9oaCYtmPet8eq1+5/tbqDSrLmAEzVPL8 UnqKHh0r9Iw5CP0VsjqN1BdvAyIstGJpSjNcw0yhmJgTXYxSgJRAPJ5ACPdp+qM9 fh6onfNxNnIK2FpDL7WChWMIMtybVUYolYkRng/lWKLX5Tvgp+hGCu9crzEJpWF7 BZLHM7kviw888I7gwQ7gl6sOqIiMSbZ+3gk23AqNpj2Q6Fzk7KaD9H3KVZuOLMSB l0QJeequN6pWYibE6b77k6/ii+XqBGODBqtmN3YS8rrq0q5mIw1FEz76Aqwiw13O /zt/A9+itJOzuMZX2103h/QcBsMYJSzlW0PKb5UF8Q+I7q6MczfDjbI48juwwo6m Q4DwFuszM6njM9qoF2yxC+pbtPTJao8cEH3Y0UEjrWPg0FLk/lBZw+g4A9OWim+g BJ2/s6XmCfOLyjaY1tzpnc+uvs+XNFxhIsSqp25PCMv4EAfzcHjvEfzUZQvQ5E7d KkqnWIDXfLifMUIlS76VVpVUqB+4qQIDAQABAoICAFHz1ErUBKd9K4QHImxD/P9y il/PC3O8uGskFcTSMy0NvBU7ns2YgLLQXv1FztwkYp6P/cxa2m6sE8LN62d9+VwO CHOAUCMLznflfITP0lmq7oYNCzn6QnI3HiLECZZdLcP7ceQlheqI+d1uF0q20TQS o+S1GoHp7cIzH+R/n4ukASC6rMHSwjzGY8EeJeDXGerZWi0yC2+EZFsSui33u/yH v4Vb0LWQyTZ5LtfRzlpiQQp6CWUVv/xvw8yGJ12wbs8VvvDn1sWKr76AqJQU4kfb 1//SbVrdhftcF/+g0Xd6X3VEdOBEbhcVYrD5JmDy5+x/N6EvCdEzMwEVvGbV2z7/ FH9VIn5kK39b9LJPS9AD9MgYbKG6uFVQzRBysYGX8KQo1bJuIDhx+XEefSCOQ+Gx ukfVlCRXdnJy8a/W1jY0Bo6jCFy2lqp4b6fVkBR+3thvFrJXLXo1xV7jgrGOJEvh KZJW61d8jVMEeII43wrOVV0IsKp+bmbQ+eC5Rsz4Y8yBkXOqgrprUVBmU12jgFCa 1GNj5zstJ03VJxTMm2EzLBm7gVb9Fjg0O6xc8rIdF/rLLOV7rTIOcgS76B2TqP4/ v3jCwfbEwdwoWHRAF84XSpjG6P0Lh0GsdhbJwU71EV4zCvIUnd7a2zLp6Zo/3DZS 115vZEfgqyk+Pen5xL3BAoIBAQDwiX9S6lLIUBRIYOT6YfZNG7/4npkKNBY2PqGv t48l0gDcxQd70tGMw47ctUM5LQrJYDEKR3zPPX6cUn1O0UVHnP5JzLzPw8sC463U dzUVXeNKcmHDXWa3fsFGgw+mHC9KMNJCfDqZxNXNP1hndLTdaLD+7FFHva4kqQGy LJJafNT+LPveb5ywkDJ7XmAzLbSlhOGy7jNCp3P86+DEpDLvrw81PYu4WImVvIZ+ 5s8v7PffTVOg1XrmnFD7vF+ovSxnhwT2of26xPjtwbWKTIDg1ZwzCBGut46dlkOX K3HoxXzkk0UZyRv3ejcesX3A5Ib6dSzChrdw9CcC61VY01G1AoIBAQDO5fkgDbWH +sWRtSUolwtCRvGgGa+nRPQcGSoBvHXS0W9S7ruf+h59hE+TBt7B+nXmyNDNSmpa rK/XkBKxSzqaVgrIcWZdxOCS+eP0UgG7jsWwyjWaYAfP1O3Wp0O7/+uZoRQWNFXk CeAQN4AdwZ9Pn1T1Nt1Y+sOycwpD0E8g6ggfPGWNKLq6Oq8DXX5xas3OQXzGd9yu M3nNTvb+i7Aawa/Bdp0YfV0/diBhqjZ/m6704QLGE5bE9bYqYNLxT6u6iVuNZLSM Ibdw0WfV7LLt7ZVxl5I4m/F+crgRmb9Un7wdr8kLI/xXJdbPn8ufL5YDJ+WQeN6F G6g+np3fMTOlAoIBAQCSp+K/lSsAAwM61gkGODBJ9z9mwJwiwntAe5NtZYeb0ZzA /kh/0Jv/LUSvgL0J4VKQUVvVHp0UZjQJ76mDIskQzsGkEXaVXpUqn9LelggBjQsF 2xOMYCg+fMQuz73803Zpz7aC3ueD1aVdzN+DxH55+FjiNQehrB6/L2RfVBmvnijn CFpQ1tA8Ps7otTQGQDnCKXDK/by3SQ3JCbAzdMGxrZSiK3JC5YiNiTKfsO5mFB9V QPpaN48FiA1ATywr35txS7tU/JONCoeTvuWG+vohG1xvKN5PHo+PuYxgYRbEi5SI cNpSzHGGxDdTOXio4S0DC+pMeILkFZiriPyyebV5AoIBAQDMe7ZQ27vCfTKu452q FD5obr14Qmq8owWwj55YwO6iQaQJDzIY1pcz7oTHB0854FSOl4LmotmibHIOVrJi z7tHtipKGOnXWzGpkZiebD6SJHV2WSPJQ4f0/LlkIURslm9AE1dK6sbI7omo/XF9 91OA2jSZdnQl8RFhWRmYFFVgbm1Akey8KrkCPeWjKdBCQBDP/SFY9jYBZZbIN3cd 9OlESJFwX8672YtDoXg3job2b+Pm2kxngAzO9RnpoHBbVyae4gq+H/3hUaF/uzco 0xu008+TyP4XPOjc1HzfyFi1RnohzQ6iGBrZ9ufrpD8XQWy+Cbx1oUArxj3uRc46 POKRAoIBACCXn8r3LYG71D2zLkjVVB93mfcx4abY3r31m+9rXAW6GZVZS+taQLpv zLCqat2G3zBVhXy99ASPy6DcoPFtkYWjdUz9V6jbzv/MtrEtU2kTSzziBPrwU4nl fzzPCuthccNwwEYan9hE33Qx7wJp/Hjk5mllZeBC5A3HhpjWxUsgwoJZpALGQg3L KIcNLj2GJ/8NelNDFr3MhBMMW5wtoXxzktyR/b1aMd54bTYAeluzjbDqBhtyja9r bvD+0sVLbcef7UVK5ruEVakj8QuR6GClkECalGVM1hcpj8LmibxdspA3NuYMliNx SVzCqFfXsK8zOqWYSt/6D5e3BA6cM/8= -----END PRIVATE KEY----- GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/data/vcard-invalid.vcf000066400000000000000000000001661460766671100313220ustar00rootroot00000000000000BEGIN:VCARD VERSION:2.1 N:;Broken;;; FN:Broken T\EL;CELL:555-555-5555 X-KDECONNECT-TIMESTAMP:1598767164977 END:VCARD GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/data/vcard-valid.vcf000066400000000000000000000203151460766671100307710ustar00rootroot00000000000000BEGIN:VCARD VERSION:2.1 N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;=D0=9B=D0=B5=D0=BE=D0=BD=D0=B8=D0=B4=20=4E=69=6B=6F=6C=61=65=76=69=63= =68;;; FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=9B=D0=B5=D0=BE=D0=BD=D0=B8=D0=B4=20=4E=69=6B=6F=6C=61=65=76=69=63= =68 TEL;CELL:778-877-9558 TEL;WORK:778-877-9999 PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwM EAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/ 2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUF BQUFBQUFBQUFBQUFBT/wAARCABgAGADASIAAhEBAxEB/8QAHQAAAgIDAQEBAAAAAAAAAAAABw gFBgIECQMAAf/EADgQAAICAQMDAwIFAgQFBQAAAAECAwQFBhESBxMhAAgiFDEVIzJBUQlhFkJ xgRckM1JiQ5GxweH/xAAaAQACAwEBAAAAAAAAAAAAAAADBAECBQYA/8QALBEAAgIBAwIEBQUB AAAAAAAAAQIAAxEEEiExQRMiUWEjMnGB8BShscHhkf/aAAwDAQACEQMRAD8Apv8AUjQr0PwSt Ses0OpI4gzRbDj9PZYAsBtvuzePiTtuV3B489tL57I6WzNHMYm09PI0plmgmjPlGB8ffx/t+/ roJ/UtMEHSXT1eugjiOcRl52PzP+jOR8PPP4yLvLybc+CQfHrnXWOyE+fSlABrwYppD8PM67+ 2brJiOvmg8dPd+OSqLJRurEGLpI0bBSuw+zI0oT7HdSNjsxKPe+PQ9rTHXHLQlN8bSx8TwMsW yRxuXIRSR8grvx3+/wBv3HoV9FOsec6J60rZ/BSJG42isQSkmOeLkGKOPPjkFYHY7MoI8j05O e1t0e91OFtz5vVh09qWSo9JK151i4RGSOUASFOMhXsgK248BtwN9vSiqdM/TK/xCtXh/EWc+J ZGSBTsOIIIB38f29bWlmpTamxxyVg1abWYxNNxLdtC4DNsPJ2G5/29NTpTol0l0vrH6HVWqor URspXheUExHdpQ0v2AKjguwYj7ONjtsAh1t6NXemGqbiY+OfIaYmkL4zKNGB9VAQXVwASR8QS f423Pp5bVc7YUTKLNYSl0+x4rzNBbfKWu8ANg0aRwcNvHg/Jv9f339bFPN1KXAJdpLYCKFkEg CoWYNvv532UgH7eT9vB9QOb03byGj5859BkokS6ZHrpj/8Al4i6qs8jSoqrH+aqKsRUcVdfP2 LViwtOx2pY1SN2id5ohy4q7SOFC+d/C8CPP+vqDUGhA5HSGulLdbLUqlezV+pm2eJWsJHtD5Y sv7AeDsV5DbcftsGK6X1amGy9ns3cbe1FWctLlZZkFPGkc+avtsvL4sQoB3B2Kbt6QuviFaNC SVbcF2ZgFCk7eRvv/vv+/wDb1ddFaMxeprdXGV6d/I5Sfbt/Sq4BYkfEbAk/cedtvI8/t6Ru0 yEcn9o9VdYeFOJ1g0bqK5mJXtUsti8vjZS0c0dJLDxIwXiQp7QDAHbyWAJ5gL539XgWUx81jZ oGR34v5+6DbjyIO26bnfb7A+RuQPXPjRvsy07l8ZjhkYbdO3YJiIa8FlaQLuWACsvHZg268lI U/MbH1d6Xsvw2nclZNHVGssNepni1uHJIndbieIj2iDN4Qg7fsoIDA+MfFKnhj/z/AGWehjyW EH/9Q/Wsl6tpXA7RLG082QKxhAv6VRWUDym/yUqf+xf4PpMYyQABv6OnvW1KdQddcvWikSSlj I4qcBRQCSEBcuw25tzZlLnyQo9AyNiijdNyT+/rqKRisTEoXbWBMuDu4XiwY/YAb7/7epOhk5 sBZSWo7SoVCyLzZUmQPuVbjswU7fbcN/p6j0klgeQwqyuPDgAEAbj/AOx6wEzF07rMOBAH8qN 9/H8f/vo2IxJq3l6l6q29iyk4gSMRyb8SV7f+bkSST3PBCgeNv4ForZy1+BwWcTVyVqKjGwlm r2XdoAeDfMcmVYx25OKtH9nbdn2HGBMVTKiU495MiqKOEGQLGdAqhRHup47Eu3Eg7koAVBZQd OvjkaN+TU8fJGhIawHY2Ru6sYzsYzsPH3HlRxO+/qpAnozfSP3jUsBm7lDUlaLIafsUTUNi1R inlR3lLM5IQNsAxcBQAZFUlVDHiYtWdLukPu307euaRkj0xq6jNWS3k6FZfoJmBkWWSRVReKF 5EImk4cg2/gIfSHaYvUat6GpejavWmgJuNJJKyWtmEyRskRB7bGONeO+/JixYbLwtvSPVU3TL VVzLUrVjF5nHDmpqX2qyKQdmKAAiQIN90LbsCT52JVR6ACXr4M9Njrf7f9Ve3rV9fTWp44u5b jEla3Ap7cqj78WIG/6h5G4O42J9Q3TXqLmtFZBjp3YXbhMbSzRKxj/8kZiOLjcnluCPHn9/To ZCjT9z2L0TWmowZW7piO4Hp5ecVVeEpxanyiPJGcBZU8P2CNtyrbJUcx7S8brHTM1jCV7ukc0 yx2JTkZlv42Vzz7ywz1ucqs4FccWibZ+QBVfsAalNu23rCqrH5Zcfbt1N0hHh8eueuY4an5Rs 8sc8CmTuoeZ5s4O/BoUMfxMZBH/d6a/AfR5ejG8L7xKitQtK7sktcONuZDAHxt4Pkeft4Prnt hermgOgF7HYb/hpdq536TlayWWpxWZ5Jn2IlhAm48CeS7IyqQoBBI8GXEf1DumcVIRWsHqKrZ fj8qtCGOMkA7vwE54A7g8U8Hc77bA+s63TsTlF4lxk9Zz/AOompZNba8z+bmURtfuy2OAO4QM xIUf+IHgf2A9QZZ40Pb2AI/VsP/n9vWcPkF9tt9x5G/rxmkHlVAH8kePXRgY4EWAwJ5HcEEHz 99x6zglIBjI3QkbgICT/AKH9v39fNtx/MdgSPtxB8f8Av6k8Hi7OcuR1MVGJbZUkI8qRs5+54 ksOR8eFG5/j7+ryZK46OvClXKxW3xJpyoz850lsJ/0/zIIiqbnkZHGzeAACVIDP5Sa6uUL1s4 mb6atK0ScEThyjiZHRd9ywXnFG+3IndVLMxHL1r2MVcuZC7Hk3kgvI5eaXIzdtju4UkhxzZuR 3IHy23JGwJHtd01SqU7k6SWJ/pLSQyPGI5YhuJN1JDg/qRdmA2YFj8SArV4k4muVn1PkZr8ta rEJVKrHUENVEKqqjZBsCBupP7n5Mx/U3ow19E9KNP5m3S1VqXKdyJIZXq1VDyxbyMXQShCsjc HQjiyBvJdYySsUFj8XJpmvDG0KYeZJwkjGZY7NaTfirmNmMyMGSFy68eJPHiFbdrJp6lj792v Xu4SrnM3DHM9iKRF5QBZ/ywrMzGzI4fiqMFLO8AJA5CRO2zPGcD2jKVsOcZjiw+3Ppbovplpr UNLqxJgaGo2it0KEdR7K2zx/LWRowJOXEyIAzBQ0nnmRu4F6ge9OWlqK9BhMjHmsNTm4VLleN ojbB8nksgDKB8uOw+Pjz5BDFah01hs31o6b4XIVqsmO6U4S5mc/WoVBLTETpXNaIDjsVaInz+ 7K+xAAIUDF4XQOV68/iOGd8bp+bUkUcVTFMSopvIp7DRNuxYHkpkjLR/HcBQV3ylrrcbrcnAz DU2PW3k4zL50zzV/rhnsTqTM9O7ORw0VpsR+OzAWEEz8ZI90YfF17bnmS48gbLuxLsaf6H43A 2FtVOEVeXaREaQpIiqu/bO7cd1Ztxv5G36iD4ndbZiqPZxisxR0jXx9i3laVmviYeSOWkshop H2VXWXtujsCAUffyeIY2el9VlMRXn+ljrPKFeepOpV4HIHMkEjY+NzuP5B8/ZK1/EwAMD0lgW Qs+e84LROIof5/n14PKvMMBuP4bz6lNOacy+tMrVw+Dx0+TyNhwkcFdOTEk7D/QefufHpi9C+ z2PFrQyevbNm9BakSJcTpzYurEblJLUvCBZNioSNHbm0kYDDmvLqntSv5jM2L1o/Qmc17farh qTWmjXnLKzrHFEu4BZ3YhVHkfc+inlfarrjS1CtkKmUwN/wCpTh2qOSUuqvFMx58gqqvCGf5k 8dlJ32IPop29Q29EYO3+G6UsaT0jEk0uO+qqwwwZGUbvEZDZdHnZQs0bBHdh8lC8gAKBJrrK5 HWskdsJms5Y4y0rctr6hLBDCQM03z3biZI24bL85Pmp7pdQ3WOfJgQyJuPMF1fKX8vdhrSxV7 F2LaruYY5RKe5zHNtt3A4n5Et4A+XHYerdncNJpvFZ7E5vD1hZsbO+QnslJwyOWWRVTdVRlRR sV3dmbbkwTtlPod7W01rkcdls9kIlxFjJw0hcr3IkrtGGhZ27zeCeHc4/lnk/bB5FuJvXU3or pbS+k85no85DXt4mpHXxAuYmnwvxh6kU5aJl5SFe7KvDiWR4nAHBV9AbWVCzYDNAaOzZuYRaN KQx5Cpp2RZqEFiW6yRQdkkVAApWR2EZZgXIXyW3AKn7fF2NKx1tAdDs91Flw1cwrDFKtSKQo0 rQlQOR7iOUZJADy5luR5L5IKiaOOKqZD6SvBLD9VDWlSd4OUomdlMaFDsBHzBbY/ELwcmRhHs xPuL12MD7ZPwz6dEuWJlw0RDizw3dLBIfltGwCyq23I7lAdipIS1TGy5Kx3P7TTor8PTO57SF 1/1am6Y9I9Y49ssMn1P6jzV57+Qx1nl2aqL+VGZIwQ3GP4+CrM7cjyCuGIv9ProQMrgbU2o8T G8WXtSWvw+7TBiuQQ1YCiEnY7Mb/IFRsO3KpLcvAQ9tXthyfUC7PDmqrQ2LFWCOtNFISKyPKy g/lqwLskE/kkBUJJJJ29dPtSW6XR/SlTSekVpyaokmWpLe7agQmaZVffbcKQ7xAAAk7jf7MwB qLlRDWhzzyf6ETWktYvlwT2/syp+71dRZTAYHp5pLUl3CZa1Klyxl6VbnJYmXcMg2KqpLBSQC B8uO3nZliy3SzW3TPp9k8rr3rprOrDSZRUSGaSHfjuxQCRydiOI+y7E7kFRy9WzSeS1xIgetb is5aoIac09hSOx3/lHLGPkihiZmaUiX9gCPl6CnvO1/m+qGTxGgrN8qFyEdWKZYyIpW5bHusD 5Kcwx+Ow2Y7DcbpUWPa4TOB9BNazTfp0BC5Pv3l0/xrf0LpKlhNK6JbECAi3RwjaagyE6SryJ j7jNIJCWR1M4RdiGAVnRwu1kZNVy/R28dJ9T1DxkdirRq2pyatBZ2VPqyWbixEdow/KOLYunc jMTJHCbLdChTyc1CtJaOTzhqG/euMJZI6qiIOsp57hn2X+WUSDZkViAunuO69/4L1dpbH4Rak 2Ulyt2xdx6RKYp60kzwxpIm0ZYhUYoA6/PYg7cSdZFLHAE4gWbukuuo+lFLWesdc4Ez6loZ2h Q+jheKUpcyAs23rhJ4Tv208bxmEiIxpEG4RxlCvlPo7puDVWqclo6nkrWP05JZoJPMEMomhjY SMGjIWduzDLY4gKGA3B3JVmJ92PuuwHSr3DVs3jqf+Jja/DZcnYLiSBoYZTPFBCOQ4PGHSZHI 8sxHgluC2dI+rdw9KrPcsQ5bNYzqBV17Nbsv9NVgPb4Sy2ZBtwDMyoI0HJmbZNyADcV2lCyng x+m7YckZhx9u0uH1xPqSnk2gwumcUi5SedLkcctSOEicSwcWBAlCHtEAKqksCR22YT9ROq1Lq XpiXL0bUFivhsnPmcoliGYwWGsNHNEj9oBGnSRrkMccr8O3XkZWPMs2z7eNaVchmLmrtQ2alG TLGe7XrSU2kjNwyAyvHD3u5KZkjsQK7A855Y0U/lPwmunWs+mPUPHY3Gt09a5Xp30NbJ1GirZ E85LpTurGVjAijSNyqltizuPIChU1rpmdmUkcfnP57TbFturKgNziDHp5o6qlqvmoK8VqTu1Z kmtJOixVtomRgocq/EyiPj5XjGgG+5PrZ0/hs3r33SaF0DqCOJ6v1aWIBYPMTB64kSVzvuxPE NsSfJIblsd3Fy3TzS69MLWf0nFTqYulHJJLTrWn5TSGNGCLIvAAg/TcnIOwUhvsNlT6TW6Q93 vT3JakjjkpYuGw80SP3CZkksKgVV/zd10bgB4JYfsdhae83tZYewIHqOI3qlWmpK09QT6HmOn h+qGDxGoNVZqCpZrWtA6Jikr1a78ZVs2IpTOxUht5R2PDMpPzO6+W7ikam95OttN5fFZeGN8n hslYbNpFkInqs0kUzxKrDkQrNHBDIQu4YuPG7bm5+7zrND0Z1Jm6ml4FgvZq5GuSjj/AOWeei iQFFaSIq5Rz3YX4sNwG2ZSAQrWnup1zRODjGmLtj8CsW5iFnbnNRlkWCRI37fEc+ULqsnFVkA YgIQyxmo0m+neVz6An87xVNSiXnLY9+sL9z3f4JbN2CKpPQaGR4Y7TEys4j+MUqqq8Qe2FQBu R2LDmo9CfRXU85fqxjctYefIVsa5ajSmUlubNwBUKWJP6fG7MWbxy9afWfqXk+p+oMBksnfkg S/GkksUUK1xyJG8yqoC8OYZf1EkwFiV3UBioun+kOmGi8frSapV/FOwTjxciLRV0WP/AKjcW5 u3xCId1I8vyYBEa3hU6ZB5TubjrmOeNbqrCA42oQemPf1jeanwF2KSO5Q+e8UcadgkrxCIG+P 3Ibtjff8AuPSSax9nee1RrR1p2rMEsM8TcewWGx38K3LiiqebHz4LPsG229SWjf6i1NpK/wDi vSctOTsstizp9ldJ3LbgiCUrx+5/9Q/q/t6MGhffX0tvWIXu5qXT6xygAW8bIjSqBsHIgWRdz 9gD+kAHz9vR/j1HO0/zOJSrb0MWnI+zPU+pNZNiMnqejwx1QU47FWFJJHdQTHGIhIrFNuQBG5 UKqhT4ACfU3oNqzpbrytpLI1JHvXlRqR4mJbaMxVGTnt4ZhsN/7fY7gOv1h6udIcVJY1b091/ XxnUmRG+mmxcLSV91Ys0csksY+JeJSNwf8rADcM37H72unOt8ng8trzT2HoauxFR61fUlBBe7 a83YqIHXhIHX4jkFIMjEOhBPqV1eoTnYSPpzNFNNvGdwERbU/SrWWlXuY67jbTw0rslaaOue6 sdhV+QZUJ4txG/kDdRuPHol+2mLIYHUc9DIfU0IbBfi8hRVrWYg/KOaOQqRzVHXwRy4MuzcW4 NzlPfX0R0ZfyOextHJav1XeQKJnow04o9y4PdKqFkfaGtyfgSwSPbwpBV/SfuZ0viL9i/a00Z rH1fdWPlurgksdh9h8izb7A+QPO3qbLr9RSVNJ5jOkSuq3c1gGI9HSz6zE9LsJjp2lkFG3DGI pHLO8W8iWIgdwCBx+O+2+yIA23yV/qTS0j0izZydySHO5KWlYyGPxcTho8bObMXdlAU8hyEUo J8kGJgx/XtEaz97ebylmBcLjxHchhMS2JYEPbRn3TY7NxALclceSCNv1eRbntRSaMy1x9RaeG UmvxkccsRJGIOTHsKYm2VSV5owA2c+VYDl6S0mjettz8Z7RrWagWjanQd5RdYZX/E31mWdZhS szrair1p+9DQWR5TJEFZnaMK3xQOylgqtseQ2Inte6V47Xt+X8StnHrGqzo00YKzETKoHIbMI jH9UHPMA8f3KAHDI6St4r2/X6+QhEeWsWmviKRgstTiwMkMobiqs8YSX95T2VXiq77y/Qi5LY 6XZm5hsqK+oMQVlaouL7s9gBw0SpOHBA5iPYbf5W8qB8tS+0+A3hnHOIDSUgahRYM8Zmt7v9H 09N67uZevap8rV11hSnD2pZIxEilmZD224ujAsqqXcyMSdwRt6U11kvcJoyXRt+s0l6pDBDDY rRP8AoQBELBWPNhwHhU+W7sd2Zm9VTrD1SPUjTtWG3jjNqGq5WeTuOixR7cyY4dzvuSN2Y+Ao AUDyfLoLrSHpJ1hxzXsZ9fUyFcULlazAqOolUD4hhtuCR4I+4/cgH1Va2OmG8eden2hrbEXVN sPkbr9/9n//2Q== X-KDECONNECT-TIMESTAMP:1598767164977 END:VCARD GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/000077500000000000000000000000001460766671100270345ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/backend.js000066400000000000000000000371651460766671100307750ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Config from '../config.js'; const Core = await import(`file://${Config.PACKAGE_DATADIR}/service/core.js`); // Retain compatibility with GLib < 2.80, which lacks GioUnix let GioUnix; try { GioUnix = (await import('gi://GioUnix')).default; } catch (e) { GioUnix = { InputStream: Gio.UnixInputStream, OutputStream: Gio.UnixOutputStream, }; } /** * TCP Port Constants */ const DEFAULT_PORT = 2716; const TRANSFER_MIN = 2739; const TRANSFER_MAX = 2764; /** * A simple IP-based backend for tests. This should ostensibly be kept up to * date with backends/lan.js as it is essentially a clone without the TLS parts. */ export const ChannelService = GObject.registerClass({ GTypeName: 'GSConnectMockChannelService', Properties: { 'port': GObject.ParamSpec.uint( 'port', 'Port', 'The port used by the service', GObject.ParamFlags.READWRITE, 0, GLib.MAXUINT16, DEFAULT_PORT ), }, }, class MockChannelService extends Core.ChannelService { _init(params = {}) { super._init(params); // this._tcp = null; this._udp4 = null; this._udp6 = null; } get channels() { if (this._channels === undefined) this._channels = new Map(); return this._channels; } get name() { return 'Mock Backend'; } get port() { if (this._port === undefined) this._port = DEFAULT_PORT; return this._port; } set port(port) { if (this.port === port) return; this._port = port; } _initTcpListener() { this._tcp = new Gio.SocketService(); // NOTE: we brute-force an open port so tests can run concurrently while (true) { try { this._tcp.add_inet_port(this.port, null); break; } catch (e) { this._port++; } } this._tcp.connect('incoming', this._onIncomingChannel.bind(this)); } async _onIncomingChannel(listener, connection) { try { const host = connection.get_remote_address().address.to_string(); // Create a channel const channel = new Channel({ backend: this, host: host, port: this.port, }); // Accept the connection await channel.accept(connection); channel.identity.body.tcpHost = channel.host; channel.identity.body.tcpPort = this.port; this.channel(channel); } catch (e) { logError(e); } } _initUdpListener() { // Default broadcast address this._udp_address = Gio.InetSocketAddress.new_from_string( '255.255.255.255', this.port); try { this._udp6 = Gio.Socket.new(Gio.SocketFamily.IPV6, Gio.SocketType.DATAGRAM, Gio.SocketProtocol.UDP); this._udp6.set_broadcast(true); // Bind the socket const inetAddr = Gio.InetAddress.new_any(Gio.SocketFamily.IPV6); const sockAddr = Gio.InetSocketAddress.new(inetAddr, this.port); this._udp6.bind(sockAddr, false); // Input stream this._udp6_stream = new Gio.DataInputStream({ base_stream: new GioUnix.InputStream({ fd: this._udp6.fd, close_fd: false, }), }); // Watch socket for incoming packets this._udp6_source = this._udp6.create_source(GLib.IOCondition.IN, null); this._udp6_source.set_callback(this._onIncomingIdentity.bind(this, this._udp6)); this._udp6_source.attach(null); } catch (e) { this._udp6 = null; } // Our IPv6 socket also supports IPv4; we're all done if (this._udp6 && this._udp6.speaks_ipv4()) { this._udp4 = null; return; } try { this._udp4 = Gio.Socket.new(Gio.SocketFamily.IPV4, Gio.SocketType.DATAGRAM, Gio.SocketProtocol.UDP); this._udp4.set_broadcast(true); // Bind the socket const inetAddr = Gio.InetAddress.new_any(Gio.SocketFamily.IPV4); const sockAddr = Gio.InetSocketAddress.new(inetAddr, this.port); this._udp4.bind(sockAddr, false); // Input stream this._udp4_stream = new Gio.DataInputStream({ base_stream: new GioUnix.InputStream({ fd: this._udp4.fd, close_fd: false, }), }); // Watch input socket for incoming packets this._udp4_source = this._udp4.create_source(GLib.IOCondition.IN, null); this._udp4_source.set_callback(this._onIncomingIdentity.bind(this, this._udp4)); this._udp4_source.attach(null); } catch (e) { this._udp4 = null; // We failed to get either an IPv4 or IPv6 socket to bind if (this._udp6 === null) throw e; } } _onIncomingIdentity(socket) { let host, data, packet; // Try to peek the remote address try { host = socket.receive_message([], Gio.SocketMsgFlags.PEEK, null)[1] .address.to_string(); } catch (e) { logError(e); } // Whether or not we peeked the address, we need to read the packet try { if (socket === this._udp6) data = this._udp6_stream.read_line_utf8(null)[0]; else data = this._udp4_stream.read_line_utf8(null)[0]; // Discard the packet if we failed to peek the address if (host === undefined) return; packet = new Core.Packet(data); packet.body.tcpHost = host; this._onIdentity(packet); } catch (e) { logError(e); } return GLib.SOURCE_CONTINUE; } async _onIdentity(packet) { try { // Bail if the deviceId is missing if (!packet.body.hasOwnProperty('deviceId')) return; // Silently ignore our own broadcasts if (packet.body.deviceId === this.identity.body.deviceId) return; // Create a new channel const channel = new Channel({ backend: this, host: packet.body.tcpHost, port: packet.body.tcpPort, identity: packet, }); // Check if channel is already open with this address if (this.channels.has(channel.address)) return; this._channels.set(channel.address, channel); // Open a TCP connection const address = Gio.InetSocketAddress.new_from_string( packet.body.tcpHost, packet.body.tcpPort); const client = new Gio.SocketClient({enable_proxy: false}); const connection = await client.connect_async(address, this.cancellable); // Connect the channel and attach it to the device on success await channel.open(connection); this.channel(channel); } catch (e) { logError(e); } } broadcast(address = null) { try { // Try to parse strings as : if (typeof address === 'string') { const [host, portstr] = address.split(':'); const port = parseInt(portstr) || this.port; address = Gio.InetSocketAddress.new_from_string(host, port); } // Broadcast to the network if no address is specified if (!(address instanceof Gio.InetSocketAddress)) address = this._udp_address; // Broadcast on each open socket if (this._udp6 !== null) this._udp6.send_to(address, this.identity.serialize(), null); if (this._udp4 !== null) this._udp4.send_to(address, this.identity.serialize(), null); } catch (e) { logError(e, address); } } buildIdentity() { this._identity = new Core.Packet({ id: 0, type: 'kdeconnect.identity', body: { deviceId: this.id, deviceName: this.name, deviceType: 'desktop', protocolVersion: 7, incomingCapabilities: [], outgoingCapabilities: [], tcpPort: this.port, }, }); } start() { if (this.active) return; // Start TCP/UDP listeners if (this._tcp === null) this._initTcpListener(); if (this._udp4 === null && this._udp6 === null) this._initUdpListener(); this._active = true; this.notify('active'); } stop() { if (this._tcp !== null) { this._tcp.stop(); this._tcp.close(); this._tcp = null; } if (this._udp6 !== null) { this._udp6_source.destroy(); this._udp6_stream.close(null); this._udp6.close(); this._udp6 = null; } if (this._udp4 !== null) { this._udp4_source.destroy(); this._udp4_stream.close(null); this._udp4.close(); this._udp4 = null; } for (const channel of this.channels.values()) channel.close(); this._active = false; this.notify('active'); } destroy() { try { this.stop(); } catch (e) { logError(e); } } }); /** * A simple IP-based channel for tests */ export const Channel = GObject.registerClass({ GTypeName: 'GSConnectMockChannel', }, class MockChannel extends Core.Channel { _init(params) { super._init(); Object.assign(this, params); } get address() { return `mock://${this.host}:${this.port}`; } get host() { if (this._host === undefined) this._host = null; return this._host; } set host(host) { this._host = host; } get port() { if (this._port === undefined) { if (this.identity && this.identity.body.tcpPort) this._port = this.identity.body.tcpPort; else return DEFAULT_PORT; } return this._port; } set port(port) { this._port = port; } async accept(connection) { try { this._connection = connection; this.backend.channels.set(this.address, this); this.input_stream = new Gio.DataInputStream({ base_stream: this._connection.get_input_stream(), }); const [data] = await this.input_stream.read_line_async( GLib.PRIORITY_DEFAULT, this.cancellable); this.identity = new Core.Packet(data); if (!this.identity.body.deviceId) throw new Error('missing deviceId'); } catch (e) { this.close(); return e; } } async open(connection) { try { this._connection = connection; this.backend.channels.set(this.address, this); this.input_stream = new Gio.DataInputStream({ base_stream: this._connection.get_input_stream(), }); await connection.output_stream.write_all_async( this.backend.identity.serialize(), GLib.PRIORITY_DEFAULT, this.cancellable); } catch (e) { this.close(); return e; } } close() { if (this.closed) return; this._closed = true; this.notify('closed'); this.backend.channels.delete(this.address); this.cancellable.cancel(); // These calls are not Promisified, so they can finish themselves if (this._connection) this._connection.close_async(GLib.PRIORITY_DEFAULT, null, null); if (this.input_stream) this.input_stream.close_async(GLib.PRIORITY_DEFAULT, null, null); if (this.output_stream) this.output_stream.close_async(GLib.PRIORITY_DEFAULT, null, null); } async download(packet, target, cancellable = null) { const address = Gio.InetSocketAddress.new_from_string(this.host, packet.payloadTransferInfo.port); const client = new Gio.SocketClient({enable_proxy: false}); const connection = await client.connect_async(address, cancellable); // Start the transfer const transferredSize = await connection.output_stream.splice_async( target, connection.input_stream, (Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET), GLib.PRIORITY_DEFAULT, cancellable); if (transferredSize !== packet.payloadSize) { throw new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.PARTIAL_INPUT, message: 'Transfer incomplete', }); } } async upload(packet, source, size, cancellable = null) { // Start listening on the first available port between 1739-1764 const listener = new Gio.SocketListener(); let port = TRANSFER_MIN; while (port <= TRANSFER_MAX) { try { listener.add_inet_port(port, null); break; } catch (e) { if (port < TRANSFER_MAX) { port++; continue; } else { throw e; } } } // Listen for the incoming connection const acceptConnection = listener.accept_async(cancellable); // Notify the device we're ready packet.body.payloadHash = this.checksum; packet.payloadSize = size; packet.payloadTransferInfo = {port: port}; const sendPacket = this.sendPacket(new Core.Packet(packet), cancellable); // Accept the connection and configure the channel const [, connection] = await Promise([sendPacket, acceptConnection]); // Start the transfer const transferredSize = await connection.output_stream.splice_async( source, (Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET), GLib.PRIORITY_DEFAULT, cancellable); if (transferredSize !== size) { throw new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.PARTIAL_INPUT, message: 'Transfer incomplete', }); } } async rejectTransfer(packet) { try { if (!packet || !packet.hasPayload()) return; if (packet.payloadTransferInfo.port === undefined) return; const address = Gio.InetSocketAddress.new_from_string(this.host, packet.payloadTransferInfo.port); const client = new Gio.SocketClient({enable_proxy: false}); const connection = await client.connect_async(address, this.cancellable); connection.close_async(GLib.PRIORITY_DEFAULT, null, null); } catch (e) { logError(e, this.address); } } }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/000077500000000000000000000000001460766671100312215ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/clipboard.js000066400000000000000000000014761460766671100335260ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GObject from 'gi://GObject'; const Component = GObject.registerClass({ GTypeName: 'MockClipboard', Properties: { 'text': GObject.ParamSpec.string( 'text', 'Text Content', 'The current text content of the clipboard', GObject.ParamFlags.READWRITE, '' ), }, }, class MockClipboard extends GObject.Object { get text() { if (this._text === undefined) this._text = 'initial'; return this._text; } set text(content) { if (this.text === content) return; this._text = content; this.notify('text'); } }); export default Component; GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/index.js000066400000000000000000000007151460766671100326710ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later export * as clipboard from './clipboard.js'; export * as input from './input.js'; export * as mpris from './mpris.js'; export * as notification from './notification.js'; export * as pulseaudio from './pulseaudio.js'; export * as session from './session.js'; export * as sound from './sound.js'; export * as upower from './upower.js'; GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/input.js000066400000000000000000000005101460766671100327120ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later export default class Component { clickPointer() {} doubleclickPointer() {} pressPointer() {} releasePointer() {} movePointer() {} scrollPointer() {} pressKeys() {} } GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/mpris.js000066400000000000000000000104141460766671100327110ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GObject from 'gi://GObject'; import Config from '../../config.js'; const MPRIS = await import(`file://${Config.PACKAGE_DATADIR}/service/components/mpris.js`); const MockMediaPlayer = GObject.registerClass({ GTypeName: 'MockMediaPlayer', }, class MockMediaPlayer extends MPRIS.Player { _init(identity) { super._init(); this._Identity = identity; this._Position = 0; } /** * Update the player with an object of properties and values. * * @param {Object} obj - A dictionary of properties */ update(obj) { for (const [propertyName, propertyValue] of Object.entries(obj)) this[`_${propertyName}`] = propertyValue; // An arbitrary property to notify this.notify('Volume'); } Next() { if (!this.CanGoNext) throw new GObject.NotImplementedError(); this.Metadata['xesam:title'] = 'Track 2'; this.notify('Metadata'); } Previous() { if (!this.CanGoPrevious) throw new GObject.NotImplementedError(); this.Metadata['xesam:title'] = 'Track 1'; this.notify('Metadata'); } Pause() { if (!this.CanPause) throw new GObject.NotImplementedError(); this._PlaybackStatus = 'Paused'; this.notify('PlaybackStatus'); } PlayPause() { if (!this.CanPlay && !this.CanPause) throw new GObject.NotImplementedError(); if (this.PlaybackStatus === 'Playing') this._PlaybackStatus = 'Paused'; else this._PlaybackStatus = 'Playing'; this.notify('PlaybackStatus'); } Stop() { this._PlaybackStatus = 'Stopped'; this.notify('PlaybackStatus'); } Play() { if (!this.CanPlay) throw new GObject.NotImplementedError(); this._PlaybackStatus = 'Playing'; this.notify('PlaybackStatus'); } Seek(offset) { if (!this.CanSeek) throw new GObject.NotImplementedError(); this._Position += offset; this.emit('Seeked', offset); } SetPosition(trackId, position) { const offset = this._Position - position; this.Seek(offset); } }); const Component = GObject.registerClass({ GTypeName: 'MockMPRISManager', Signals: { 'player-added': { param_types: [GObject.TYPE_OBJECT], }, 'player-removed': { param_types: [GObject.TYPE_OBJECT], }, 'player-changed': { param_types: [GObject.TYPE_OBJECT], }, 'player-seeked': { param_types: [GObject.TYPE_OBJECT, GObject.TYPE_INT64], }, }, }, class MockMPRISManager extends GObject.Object { _init() { super._init(); this._players = new Map(); this._paused = new Map(); } addPlayer(identity) { const player = new MockMediaPlayer(identity); player.connect('notify', () => this.emit('player-changed', player)); player.connect('Seeked', this.emit.bind(this, 'player-seeked')); this._players.set(identity, player); this.emit('player-added', player); return player; } removePlayer(identity) { const player = this._players.get(identity); if (player === undefined) return; this._players.delete(identity); this.emit('player-removed', player); player.run_dispose(); } getPlayer(identity) { for (const player of this._players.values()) { if (player.Identity === identity) return player; } return null; } hasPlayer(identity) { for (const player of this._players.values()) { if (player.Identity === identity) return true; } return false; } getIdentities() { const identities = []; for (const player of this._players.values()) { const identity = player.Identity; if (identity) identities.push(identity); } return identities; } pauseAll() { } unpauseAll() { } }); export default Component; notification.js000066400000000000000000000012371460766671100341710ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; const Component = GObject.registerClass({ GTypeName: 'GSConnectMockNotificationListener', Signals: { 'notification-added': { flags: GObject.SignalFlags.RUN_LAST, param_types: [GLib.Variant.$gtype], }, }, }, class MockListener extends GObject.Object { fakeNotification(notif) { const variant = GLib.Variant.full_pack(notif); this.emit('notification-added', variant); } }); export default Component; GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/pulseaudio.js000066400000000000000000000115431460766671100337350ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GObject from 'gi://GObject'; const Tweener = imports.tweener.tweener; class MockStream { constructor(mixer, id) { this._mixer = mixer; this._id = id; this._max = this._mixer.get_vol_max_norm(); } get display_name() { return `Stream ${this.id}`; } get id() { if (this._id === undefined) this._id = Math.floor(Math.random() * 100); return this._id; } get is_muted() { return this.muted; } get muted() { if (this._muted === undefined) this._muted = false; return this._muted; } set muted(muted) { if (this.muted === muted) return; this._muted = muted; } get name() { return `${this.id}`; } // Volume is a double in the range 0-1 get volume() { if (this._volume === undefined) this._volume = 1.0; return this._volume; } set volume(volume) { if (this.volume === volume) return; this._volume = volume; } change_is_muted(muted) { this.muted = muted; } fade(value, duration = 1) { Tweener.removeTweens(this); if (this.volume > value) { this._mixer.fading = true; Tweener.addTween(this, { volume: value, time: duration, transition: 'easeOutCubic', onComplete: () => { this._mixer.fading = false; }, }); } else if (this.volume < value) { this._mixer.fading = true; Tweener.addTween(this, { volume: value, time: duration, transition: 'easeInCubic', onComplete: () => { this._mixer.fading = false; }, }); } } } const Component = GObject.registerClass({ GTypeName: 'GSConnectMockMixer', Signals: { 'output-added': { param_types: [GObject.TYPE_UINT], }, 'output-removed': { param_types: [GObject.TYPE_UINT], }, 'stream-changed': { param_types: [GObject.TYPE_UINT], }, }, }, class MockMixer extends GObject.Object { _init() { super._init(); this._sinks = new Map([ [0, new MockStream(this, 0)], ]); this._sources = new Map([ [0, new MockStream(this, 0)], ]); this._previousVolume = undefined; this._volumeMuted = false; this._microphoneMuted = false; } get fading() { if (this._fading === undefined) this._fading = false; return this._fading; } set fading(bool) { if (this.fading === bool) return; this._fading = bool; if (this.fading) this.emit('stream-changed', this.output.id); } get input() { if (this._input === undefined) this._input = this._sources.get(0); return this._input; } get output() { if (this._output === undefined) this._output = this._sinks.get(0); return this._output; } get_sinks() { return Array.from(this._sinks.values()); } get_vol_max_norm() { return 65536; } lookup_sink(id) { const sink = this._sinks.get(id); return sink || null; } lowerVolume(duration = 1) { try { if (this.output.volume > 0.15) { this._previousVolume = Number(this.output.volume); this.output.fade(0.15, duration); } } catch (e) { logError(e); } } muteVolume() { try { if (this.output.muted) return; this.output.muted = true; this._volumeMuted = true; } catch (e) { logError(e); } } muteMicrophone() { try { if (this.input.muted) return; this.input.muted = true; this._microphoneMuted = true; } catch (e) { logError(e); } } restore() { try { if (this._microphoneMuted) { this.input.muted = false; this._microphoneMuted = false; } if (this._volumeMuted) { this.output.muted = false; this._volumeMuted = false; } if (this._previousVolume !== undefined) { this.output.fade(this._previousVolume); this._previousVolume = undefined; } } catch (e) { logError(e); } } }); export default Component; GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/session.js000066400000000000000000000015061460766671100332440ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later export default class MockSessionComponent { get idle() { if (this._idle === undefined) this._idle = false; return this._idle; } get locked() { if (this._locked === undefined) this._locked = false; return this._locked; } get active() { // Active if not idle and not locked return !(this.idle || this.locked); } /** * Update the session with an object of properties and values. * * @param {Object} obj - A dictionary of properties */ update(obj) { for (const [propertyName, propertyValue] of Object.entries(obj)) this[`_${propertyName}`] = propertyValue; } } GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/sound.js000066400000000000000000000034601460766671100327120ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; export default class MockPlayerComponent { constructor() { this._playing = new Set(); } async playSound(name, cancellable) { try { if (!(cancellable instanceof Gio.Cancellable)) cancellable = new Gio.Cancellable(); this._playing.add(cancellable); await new Promise((resolve, reject) => { GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => { if (cancellable.is_cancelled()) { const error = new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.CANCELLED, message: 'Operation Cancelled', }); reject(error); } else { resolve(); } return GLib.SOURCE_REMOVE; }); }); } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) logError(e); } finally { this._playing.delete(cancellable); } } async loopSound(name, cancellable) { try { if (!(cancellable instanceof Gio.Cancellable)) cancellable = new Gio.Cancellable(); this._playing.add(cancellable); while (!cancellable.is_cancelled()) await this.playSound(name, cancellable); } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) logError(e); } finally { this._playing.delete(cancellable); } } } GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/components/upower.js000066400000000000000000000021531460766671100331010ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GObject from 'gi://GObject'; const Component = GObject.registerClass({ GTypeName: 'GSConnectMockBattery', Signals: { 'changed': {flags: GObject.SignalFlags.RUN_FIRST}, }, }, class MockBattery extends GObject.Object { get charging() { if (this._charging === undefined) this._charging = false; return this._charging; } get is_present() { if (this._is_present === undefined) this._is_present = true; return this._is_present; } get level() { if (this._level === undefined) this._level = -1; return this._level; } get threshold() { if (this._threshold === undefined) this._threshold = 0; return this._threshold; } update(obj) { for (const [propertyName, propertyValue] of Object.entries(obj)) this[`_${propertyName}`] = propertyValue; this.emit('changed'); } }); export default Component; GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/mpris.js000066400000000000000000000061331460766671100305270ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import Config from '../config.js'; const DBus = await import(`file://${Config.PACKAGE_DATADIR}/service/utils/dbus.js`); const MPRIS = await import(`file://${Config.PACKAGE_DATADIR}/service/components/mpris.js`); /* * A class for mirroring a remote Media Player on DBus */ const MPRISIface = Config.DBUS.lookup_interface('org.mpris.MediaPlayer2'); const MPRISPlayerIface = Config.DBUS.lookup_interface('org.mpris.MediaPlayer2.Player'); const MockPlayer = GObject.registerClass({ GTypeName: 'GSConnectMockPlayer', }, class MockPlayer extends MPRIS.Player { _init(identity) { super._init(); this._Identity = identity; this._ownerId = 0; this._connection = null; this._applicationIface = null; this._playerIface = null; } async export() { if (this._connection === null) { this._connection = await DBus.newConnection(); this._applicationIface = new DBus.Interface({ g_instance: this, g_connection: this._connection, g_object_path: '/org/mpris/MediaPlayer2', g_interface_info: MPRISIface, }); this._playerIface = new DBus.Interface({ g_instance: this, g_connection: this._connection, g_object_path: '/org/mpris/MediaPlayer2', g_interface_info: MPRISPlayerIface, }); } if (this._ownerId !== 0) return; const name = this.Identity.replace(/\W*/g, '_'); this._ownerId = Gio.bus_own_name_on_connection( this._connection, `org.mpris.MediaPlayer2.${name}`, Gio.BusNameOwnerFlags.NONE, null, null ); } unexport() { if (this._ownerId === 0) return; Gio.bus_unown_name(this._ownerId); this._ownerId = 0; } get Metadata() { if (this._Metadata === undefined) this._Metadata = {}; return this._Metadata; } Play() { printerr(`Play(): ${this.PlaybackStatus}`); if (this.PlaybackStatus === 'Playing') return; printerr('Play()'); this._PlaybackStatus = 'Playing'; this.notify('PlaybackStatus'); } Pause() { if (this.PlaybackStatus !== 'Playing') return; this._PlaybackStatus = 'Paused'; this.notify('PlaybackStatus'); } Seek(offset) { if (!this.CanSeek) return; this.emit('Seeked', offset); } destroy() { this.unexport(); if (this._connection) { this._connection.close(null, null); this._connection = null; this._applicationIface.destroy(); this._applicationIface = null; this._playerIface.destroy(); this._playerIface = null; } } }); export default MockPlayer; GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/fixtures/utils.js000066400000000000000000000244321460766671100305370ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import 'gi://Gdk?version=3.0'; import 'gi://Gtk?version=3.0'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; // Ensure the environment is prepared for testing import Config from '../config.js'; if (GLib.getenv('GSCONNECT_TEST')) { Config.PACKAGE_DATADIR = GLib.getenv('GJS_PATH'); Config.GSETTINGS_SCHEMA_DIR = GLib.getenv('GSETTINGS_SCHEMA_DIR'); } else { GLib.setenv('G_DEBUG', 'fatal-warnings,fatal-criticals', true); GLib.setenv('GSETTINGS_BACKEND', 'memory', true); GLib.setenv('NO_AT_BRIDGE', '1', true); } await import(`file://${Config.PACKAGE_DATADIR}/service/init.js`); const {default: Device} = await import(`file://${Config.PACKAGE_DATADIR}/service/device.js`); const {default: Plugin} = await import(`file://${Config.PACKAGE_DATADIR}/service/plugin.js`); const {ChannelService} = await import('./backend.js'); // Force testing under GNOME globalThis.HAVE_GNOME = true; /* * File Helpers */ function get_datadir() { const thisFile = Gio.File.new_for_uri(import.meta.url); return thisFile.get_parent().get_parent().get_child('data').get_path(); } const DATA_PATH = get_datadir(); export function getDataPath(filename) { return GLib.build_filenamev([DATA_PATH, filename]); } export function getDataUri(filename) { return `file://${getDataPath(filename)}`; } export function getDataFile(filename) { return Gio.File.new_for_path(getDataPath(filename)); } export function loadDataContents(filename) { const path = getDataPath(filename); const bytes = GLib.file_get_contents(path)[1]; return new TextDecoder().decode(bytes); } /* * Async Helpers */ Promise.idle = function (priority = GLib.PRIORITY_DEFAULT_IDLE) { return new Promise(resolve => { GLib.idle_add(priority, () => { resolve(); return GLib.SOURCE_REMOVE; }); }); }; /* * Identity Helpers */ function getDeviceType() { const types = [ 'desktop', 'laptop', 'phone', 'tablet', 'tv', ]; return types[Math.floor(Math.random() * types.length)]; } /** * Generate a pseudo-random device identity. * * @param {Object} params - Override parameters * @return {Object} A pseudo-random identity packet */ export function generateIdentity(params = {}) { const identity = { 'id': Date.now(), 'type': 'kdeconnect.identity', 'body': { 'deviceId': GLib.uuid_string_random(), 'deviceName': 'Test Device', 'deviceType': getDeviceType(), 'protocolVersion': 7, 'incomingCapabilities': [], 'outgoingCapabilities': [], }, }; for (const [key, value] of Object.entries(params)) { if (key === 'body') Object.assign(identity.body, value); else identity[key] = value; } return identity; } /** * Check if @subset is a subset of @obj. * * @param {Object} obj - The haystack to compare with * @param {Object} subset - The needle to search for * @return {boolean} %true if the object is a subset */ function isSubset(obj, subset) { for (const [key, val] of Object.entries(subset)) { if (!obj.hasOwnProperty(key)) return false; // We were only checking for the key itself if (typeof val === 'undefined') continue; if (Array.isArray(val)) { // If passed an empty array, we're expecting it to be empty if (val.length === 0 && obj[key].length !== 0) return false; // Otherwise we're looking for a subset of the array if (!val.every(n => obj[key].includes(n))) return false; continue; } // This is JSON and KDE Connect has no %null use; an object is an object if (typeof val === 'object') { if (!isSubset(obj[key], val)) return false; continue; } if (obj[key] !== val) return false; } return true; } /** * Wait for the `handlePacket` method of a device or plugin to be passed a * packet to handle. Note, the object must have an active jasmine spy. * * @param {string} type - A KDE Connect packet type * @param {Object} [body] - Packet body properties */ async function _awaitPacket(type, body = null) { while (true) { for (const [packet] of this.handlePacket.calls.allArgs()) { if (packet.type !== type) continue; if (body === null) return; if (isSubset(packet.body, body)) return; } await Promise.idle(GLib.PRIORITY_DEFAULT); } } Device.prototype.awaitPacket = _awaitPacket; Plugin.prototype.awaitPacket = _awaitPacket; /** * Create temporary directories used by GSConnect. * * @return {string} The root temporary directory */ function isolateDirectories() { const tmpdir = GLib.Dir.make_tmp('gsconnect.XXXXXX'); Config.CACHEDIR = GLib.build_filenamev([tmpdir, 'cache']); Config.CONFIGDIR = GLib.build_filenamev([tmpdir, 'config']); Config.RUNTIMEDIR = GLib.build_filenamev([tmpdir, 'runtime']); for (const path of [Config.CACHEDIR, Config.CONFIGDIR, Config.RUNTIMEDIR]) GLib.mkdir_with_parents(path, 0o755); return tmpdir; } /** * Patch in the mock components for plugin tests. */ export async function mockComponents() { const {functionOverrides} = await import(`file://${Config.PACKAGE_DATADIR}/service/components/index.js`); const MockComponents = await import('./components/index.js'); functionOverrides.acquire = function (name) { return new MockComponents[name].default(); }; functionOverrides.release = function (name) { return null; }; } /** * Recursively remove a directory. * * @param {Gio.File|string} file - The file or path to delete */ function removeDirectory(file) { try { if (typeof file === 'string') file = Gio.File.new_for_path(file); try { const iter = file.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); let info; while ((info = iter.next_file(null))) removeDirectory(iter.get_child(info)); iter.close(null); } catch (e) { } file.delete(null); } catch (e) { } } /** * A test rig with two active GSconnectChannelService instances. */ export class TestRig { /** * Create a new test rig. * * @param {boolean} [dirs] - Whether to isolate user directories */ constructor(dirs = true) { this.localService = new ChannelService({id: 'local-service'}); this.localDevice = null; this.localChannel = null; this.remoteService = new ChannelService({id: 'remote-service'}); this.remoteDevice = null; this.remoteChannel = null; if (dirs) this._tmpdir = isolateDirectories(); } /** * Prepare two devices and channels with appropriate capabilities. * * Connect the devices with `setConnected()`, pair them with `setPaired()` * and load their plugins with `loadPlugins()`. * * @param {Object} [overrides] - Capability overrides * @param {Object} overrides.localDevice - Local device overrides * @param {Object} overrides.remoteDevice - Remote device overrides * @return {Promise} A promise for the operation */ prepare(overrides = {}) { return new Promise((resolve, reject) => { const localId = this.localService.connect('channel', (service, channel) => { service.disconnect(localId); if (overrides.localDevice) Object.assign(channel.identity.body, overrides.localDevice); this.localChannel = channel; this.localDevice = new Device(channel.identity); if (this.localDevice && this.remoteDevice) resolve(); return true; }); const remoteId = this.remoteService.connect('channel', (service, channel) => { service.disconnect(remoteId); if (overrides.remoteDevice) Object.assign(channel.identity.body, overrides.remoteDevice); this.remoteChannel = channel; this.remoteDevice = new Device(channel.identity); if (this.localDevice && this.remoteDevice) resolve(); return true; }); this.localService.start(); this.remoteService.start(); this.localService.broadcast(`127.0.0.1:${this.remoteService.port}`); }); } /** * Set both devices as connected by applying the negotiated channels. * * @param {boolean} connected - %true to connect, %false to disconnect */ async setConnected(connected) { if (connected) { this.localDevice.setChannel(this.localChannel); this.remoteDevice.setChannel(this.remoteChannel); } else { this.localDevice.setChannel(null); this.remoteDevice.setChannel(null); } await Promise.idle(); } /** * Set both devices as paired by calling the internal setters. * * @param {boolean} paired - %true to pair, %false to unpair */ setPaired(paired) { this.localDevice._setPaired(paired); this.remoteDevice._setPaired(paired); } async loadPlugins() { await this.localDevice._loadPlugins(); await this.remoteDevice._loadPlugins(); } destroy() { // Local Device if (this.localDevice) this.localDevice.destroy(); if (this.localChannel) this.localChannel.close(); // Remote Device if (this.remoteDevice) this.remoteDevice.destroy(); if (this.remoteChannel) this.remoteChannel.close(); // Channel Services if (this.localService) this.localService.destroy(); if (this.remoteService) this.remoteService.destroy(); // Cleanup temporary files if (this._tmpdir) removeDirectory(this._tmpdir); } } GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/jasmine.js000066400000000000000000007627431460766671100271720ustar00rootroot00000000000000/* eslint-disable */ // SPDX-FileCopyrightText: Copyright (c) 2008-2020 Pivotal Labs // // SPDX-License-Identifier: MPL-2.0 // eslint-disable-next-line no-unused-vars export const getJasmineRequireObj = (function(jasmineGlobal) { var jasmineRequire; if ( typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined' ) { if (typeof global !== 'undefined') { jasmineGlobal = global; } else { jasmineGlobal = {}; } jasmineRequire = exports; } else { if ( typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]' ) { jasmineGlobal = window; } jasmineRequire = jasmineGlobal.jasmineRequire = {}; } function getJasmineRequire() { return jasmineRequire; } getJasmineRequire().core = function(jRequire) { var j$ = {}; jRequire.base(j$, jasmineGlobal); j$.util = jRequire.util(j$); j$.errors = jRequire.errors(); j$.formatErrorMsg = jRequire.formatErrorMsg(); j$.Any = jRequire.Any(j$); j$.Anything = jRequire.Anything(j$); j$.CallTracker = jRequire.CallTracker(j$); j$.MockDate = jRequire.MockDate(); j$.getClearStack = jRequire.clearStack(j$); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$); j$.Env = jRequire.Env(j$); j$.StackTrace = jRequire.StackTrace(j$); j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.ExpectationFilterChain = jRequire.ExpectationFilterChain(); j$.Expector = jRequire.Expector(j$); j$.Expectation = jRequire.Expectation(j$); j$.buildExpectationResult = jRequire.buildExpectationResult(j$); j$.JsApiReporter = jRequire.JsApiReporter(j$); j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim( j$ ); j$.makePrettyPrinter = jRequire.makePrettyPrinter(j$); j$.pp = j$.makePrettyPrinter(); j$.MatchersUtil = jRequire.MatchersUtil(j$); j$.matchersUtil = new j$.MatchersUtil({ customTesters: [], pp: j$.pp }); j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$); j$.MapContaining = jRequire.MapContaining(j$); j$.SetContaining = jRequire.SetContaining(j$); j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.Spec = jRequire.Spec(j$); j$.Spy = jRequire.Spy(j$); j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(j$); j$.StringMatching = jRequire.StringMatching(j$); j$.UserContext = jRequire.UserContext(j$); j$.Suite = jRequire.Suite(j$); j$.Timer = jRequire.Timer(); j$.TreeProcessor = jRequire.TreeProcessor(); j$.version = jRequire.version(); j$.Order = jRequire.Order(); j$.DiffBuilder = jRequire.DiffBuilder(j$); j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$); j$.ObjectPath = jRequire.ObjectPath(j$); j$.MismatchTree = jRequire.MismatchTree(j$); j$.GlobalErrors = jRequire.GlobalErrors(j$); j$.Truthy = jRequire.Truthy(j$); j$.Falsy = jRequire.Falsy(j$); j$.Empty = jRequire.Empty(j$); j$.NotEmpty = jRequire.NotEmpty(j$); j$.matchers = jRequire.requireMatchers(jRequire, j$); j$.asyncMatchers = jRequire.requireAsyncMatchers(jRequire, j$); return j$; }; return getJasmineRequire; })(this); getJasmineRequireObj().requireMatchers = function(jRequire, j$) { var availableMatchers = [ 'nothing', 'toBe', 'toBeCloseTo', 'toBeDefined', 'toBeInstanceOf', 'toBeFalse', 'toBeFalsy', 'toBeGreaterThan', 'toBeGreaterThanOrEqual', 'toBeLessThan', 'toBeLessThanOrEqual', 'toBeNaN', 'toBeNegativeInfinity', 'toBeNull', 'toBePositiveInfinity', 'toBeTrue', 'toBeTruthy', 'toBeUndefined', 'toContain', 'toEqual', 'toHaveSize', 'toHaveBeenCalled', 'toHaveBeenCalledBefore', 'toHaveBeenCalledOnceWith', 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith', 'toHaveClass', 'toMatch', 'toThrow', 'toThrowError', 'toThrowMatching' ], matchers = {}; for (var i = 0; i < availableMatchers.length; i++) { var name = availableMatchers[i]; matchers[name] = jRequire[name](j$); } return matchers; }; getJasmineRequireObj().base = function(j$, jasmineGlobal) { j$.unimplementedMethod_ = function() { throw new Error('unimplemented method'); }; /** * Maximum object depth the pretty printer will print to. * Set this to a lower value to speed up pretty printing if you have large objects. * @name jasmine.MAX_PRETTY_PRINT_DEPTH * @since 1.3.0 */ j$.MAX_PRETTY_PRINT_DEPTH = 8; /** * Maximum number of array elements to display when pretty printing objects. * This will also limit the number of keys and values displayed for an object. * Elements past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH * @since 2.7.0 */ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; /** * Maximum number of characters to display when pretty printing objects. * Characters past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_CHARS * @since 2.9.0 */ j$.MAX_PRETTY_PRINT_CHARS = 1000; /** * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL * @since 1.3.0 */ j$.DEFAULT_TIMEOUT_INTERVAL = 5000; j$.getGlobal = function() { return jasmineGlobal; }; /** * Get the currently booted Jasmine Environment. * * @name jasmine.getEnv * @since 1.3.0 * @function * @return {Env} */ j$.getEnv = function(options) { var env = (j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options)); //jasmine. singletons in here (setTimeout blah blah). return env; }; j$.isArray_ = function(value) { return j$.isA_('Array', value); }; j$.isObject_ = function(value) { return ( !j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value) ); }; j$.isString_ = function(value) { return j$.isA_('String', value); }; j$.isNumber_ = function(value) { return j$.isA_('Number', value); }; j$.isFunction_ = function(value) { return j$.isA_('Function', value); }; j$.isAsyncFunction_ = function(value) { return j$.isA_('AsyncFunction', value); }; j$.isTypedArray_ = function(value) { return ( j$.isA_('Float32Array', value) || j$.isA_('Float64Array', value) || j$.isA_('Int16Array', value) || j$.isA_('Int32Array', value) || j$.isA_('Int8Array', value) || j$.isA_('Uint16Array', value) || j$.isA_('Uint32Array', value) || j$.isA_('Uint8Array', value) || j$.isA_('Uint8ClampedArray', value) ); }; j$.isA_ = function(typeName, value) { return j$.getType_(value) === '[object ' + typeName + ']'; }; j$.isError_ = function(value) { if (value instanceof Error) { return true; } if (value && value.constructor && value.constructor.constructor) { var valueGlobal = value.constructor.constructor('return this'); if (j$.isFunction_(valueGlobal)) { valueGlobal = valueGlobal(); } if (valueGlobal.Error && value instanceof valueGlobal.Error) { return true; } } return false; }; j$.isAsymmetricEqualityTester_ = function(obj) { return obj && obj.asymmetricMatch ? j$.isA_('Function', obj.asymmetricMatch) : false; }; j$.getType_ = function(value) { return Object.prototype.toString.apply(value); }; j$.isDomNode = function(obj) { // Node is a function, because constructors return typeof jasmineGlobal.Node !== 'undefined' ? obj instanceof jasmineGlobal.Node : obj !== null && typeof obj === 'object' && typeof obj.nodeType === 'number' && typeof obj.nodeName === 'string'; // return obj.nodeType > 0; }; j$.isMap = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && typeof jasmineGlobal.Map !== 'undefined' && obj.constructor === jasmineGlobal.Map ); }; j$.isSet = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && typeof jasmineGlobal.Set !== 'undefined' && obj.constructor === jasmineGlobal.Set ); }; j$.isWeakMap = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && typeof jasmineGlobal.WeakMap !== 'undefined' && obj.constructor === jasmineGlobal.WeakMap ); }; j$.isDataView = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && typeof jasmineGlobal.DataView !== 'undefined' && obj.constructor === jasmineGlobal.DataView ); }; j$.isPromise = function(obj) { return ( typeof jasmineGlobal.Promise !== 'undefined' && !!obj && obj.constructor === jasmineGlobal.Promise ); }; j$.isPromiseLike = function(obj) { return !!obj && j$.isFunction_(obj.then); }; j$.fnNameFor = function(func) { if (func.name) { return func.name; } var matches = func.toString().match(/^\s*function\s*(\w+)\s*\(/) || func.toString().match(/^\s*\[object\s*(\w+)Constructor\]/); return matches ? matches[1] : ''; }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is an instance of the specified class/constructor. * @name jasmine.any * @since 1.3.0 * @function * @param {Constructor} clazz - The constructor to check against. */ j$.any = function(clazz) { return new j$.Any(clazz); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is not `null` and not `undefined`. * @name jasmine.anything * @since 2.2.0 * @function */ j$.anything = function() { return new j$.Anything(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is `true` or anything truthy. * @name jasmine.truthy * @since 3.1.0 * @function */ j$.truthy = function() { return new j$.Truthy(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything falsey. * @name jasmine.falsy * @since 3.1.0 * @function */ j$.falsy = function() { return new j$.Falsy(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is empty. * @name jasmine.empty * @since 3.1.0 * @function */ j$.empty = function() { return new j$.Empty(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is not empty. * @name jasmine.notEmpty * @since 3.1.0 * @function */ j$.notEmpty = function() { return new j$.NotEmpty(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared contains at least the keys and values. * @name jasmine.objectContaining * @since 1.3.0 * @function * @param {Object} sample - The subset of properties that _must_ be in the actual. */ j$.objectContaining = function(sample) { return new j$.ObjectContaining(sample); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value is a `String` that matches the `RegExp` or `String`. * @name jasmine.stringMatching * @since 2.2.0 * @function * @param {RegExp|String} expected */ j$.stringMatching = function(expected) { return new j$.StringMatching(expected); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value is an `Array` that contains at least the elements in the sample. * @name jasmine.arrayContaining * @since 2.2.0 * @function * @param {Array} sample */ j$.arrayContaining = function(sample) { return new j$.ArrayContaining(sample); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value is an `Array` that contains all of the elements in the sample in any order. * @name jasmine.arrayWithExactContents * @since 2.8.0 * @function * @param {Array} sample */ j$.arrayWithExactContents = function(sample) { return new j$.ArrayWithExactContents(sample); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if every key/value pair in the sample passes the deep equality comparison * with at least one key/value pair in the actual value being compared * @name jasmine.mapContaining * @since 3.5.0 * @function * @param {Map} sample - The subset of items that _must_ be in the actual. */ j$.mapContaining = function(sample) { return new j$.MapContaining(sample); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if every item in the sample passes the deep equality comparison * with at least one item in the actual value being compared * @name jasmine.setContaining * @since 3.5.0 * @function * @param {Set} sample - The subset of items that _must_ be in the actual. */ j$.setContaining = function(sample) { return new j$.SetContaining(sample); }; j$.isSpy = function(putativeSpy) { if (!putativeSpy || !putativeSpy.and) { return false; } return ( putativeSpy.and instanceof j$.SpyStrategy && putativeSpy.calls instanceof j$.CallTracker ); }; }; getJasmineRequireObj().util = function(j$) { var util = {}; util.inherit = function(childClass, parentClass) { var Subclass = function() {}; Subclass.prototype = parentClass.prototype; childClass.prototype = new Subclass(); }; util.htmlEscape = function(str) { if (!str) { return str; } return str .replace(/&/g, '&') .replace(//g, '>'); }; util.argsToArray = function(args) { var arrayOfArgs = []; for (var i = 0; i < args.length; i++) { arrayOfArgs.push(args[i]); } return arrayOfArgs; }; util.isUndefined = function(obj) { return obj === void 0; }; util.arrayContains = function(array, search) { var i = array.length; while (i--) { if (array[i] === search) { return true; } } return false; }; util.clone = function(obj) { if (Object.prototype.toString.apply(obj) === '[object Array]') { return obj.slice(); } var cloned = {}; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { cloned[prop] = obj[prop]; } } return cloned; }; util.cloneArgs = function(args) { var clonedArgs = []; var argsAsArray = j$.util.argsToArray(args); for (var i = 0; i < argsAsArray.length; i++) { var str = Object.prototype.toString.apply(argsAsArray[i]), primitives = /^\[object (Boolean|String|RegExp|Number)/; // All falsey values are either primitives, `null`, or `undefined. if (!argsAsArray[i] || str.match(primitives)) { clonedArgs.push(argsAsArray[i]); } else { clonedArgs.push(j$.util.clone(argsAsArray[i])); } } return clonedArgs; }; util.getPropertyDescriptor = function(obj, methodName) { var descriptor, proto = obj; do { descriptor = Object.getOwnPropertyDescriptor(proto, methodName); proto = Object.getPrototypeOf(proto); } while (!descriptor && proto); return descriptor; }; util.objectDifference = function(obj, toRemove) { var diff = {}; for (var key in obj) { if (util.has(obj, key) && !util.has(toRemove, key)) { diff[key] = obj[key]; } } return diff; }; util.has = function(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); }; util.errorWithStack = function errorWithStack() { // Don't throw and catch if we don't have to, because it makes it harder // for users to debug their code with exception breakpoints. var error = new Error(); if (error.stack) { return error; } // But some browsers (e.g. Phantom) only provide a stack trace if we throw. try { throw new Error(); } catch (e) { return e; } }; function callerFile() { var trace = new j$.StackTrace(util.errorWithStack()); return trace.frames[2].file; } util.jasmineFile = (function() { var result; return function() { if (!result) { result = callerFile(); } return result; }; })(); function StopIteration() {} StopIteration.prototype = Object.create(Error.prototype); StopIteration.prototype.constructor = StopIteration; // useful for maps and sets since `forEach` is the only IE11-compatible way to iterate them util.forEachBreakable = function(iterable, iteratee) { function breakLoop() { throw new StopIteration(); } try { iterable.forEach(function(value, key) { iteratee(breakLoop, value, key, iterable); }); } catch (error) { if (!(error instanceof StopIteration)) throw error; } }; return util; }; getJasmineRequireObj().Spec = function(j$) { function Spec(attrs) { this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.description = attrs.description || ''; this.queueableFn = attrs.queueableFn; this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return { befores: [], afters: [] }; }; this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.expectationResultFactory = attrs.expectationResultFactory || function() {}; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; this.timer = attrs.timer || new j$.Timer(); if (!this.queueableFn.fn) { this.pend(); } /** * @typedef SpecResult * @property {Int} id - The unique id of this spec. * @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} fullName - The full description including all ancestors of this spec. * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec. * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} */ this.result = { id: this.id, description: this.description, fullName: this.getFullName(), failedExpectations: [], passedExpectations: [], deprecationWarnings: [], pendingReason: '', duration: null, properties: null }; } Spec.prototype.addExpectationResult = function(passed, data, isError) { var expectationResult = this.expectationResultFactory(data); if (passed) { this.result.passedExpectations.push(expectationResult); } else { this.result.failedExpectations.push(expectationResult); if (this.throwOnExpectationFailure && !isError) { throw new j$.errors.ExpectationFailed(); } } }; Spec.prototype.setSpecProperty = function(key, value) { this.result.properties = this.result.properties || {}; this.result.properties[key] = value; }; Spec.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; Spec.prototype.expectAsync = function(actual) { return this.asyncExpectationFactory(actual, this); }; Spec.prototype.execute = function(onComplete, excluded, failSpecWithNoExp) { var self = this; var onStart = { fn: function(done) { self.timer.start(); self.onStart(self, done); } }; var complete = { fn: function(done) { self.queueableFn.fn = null; self.result.status = self.status(excluded, failSpecWithNoExp); self.result.duration = self.timer.elapsed(); self.resultCallback(self.result, done); } }; var fns = this.beforeAndAfterFns(); var regularFns = fns.befores.concat(this.queueableFn); var runnerConfig = { isLeaf: true, queueableFns: regularFns, cleanupFns: fns.afters, onException: function() { self.onException.apply(self, arguments); }, onComplete: function() { onComplete( self.result.status === 'failed' && new j$.StopExecutionError('spec failed') ); }, userContext: this.userContext() }; if (this.markedPending || excluded === true) { runnerConfig.queueableFns = []; runnerConfig.cleanupFns = []; } runnerConfig.queueableFns.unshift(onStart); runnerConfig.cleanupFns.push(complete); this.queueRunnerFactory(runnerConfig); }; Spec.prototype.onException = function onException(e) { if (Spec.isPendingSpecException(e)) { this.pend(extractCustomPendingMessage(e)); return; } if (e instanceof j$.errors.ExpectationFailed) { return; } this.addExpectationResult( false, { matcherName: '', passed: false, expected: '', actual: '', error: e }, true ); }; Spec.prototype.pend = function(message) { this.markedPending = true; if (message) { this.result.pendingReason = message; } }; Spec.prototype.getResult = function() { this.result.status = this.status(); return this.result; }; Spec.prototype.status = function(excluded, failSpecWithNoExpectations) { if (excluded === true) { return 'excluded'; } if (this.markedPending) { return 'pending'; } if ( this.result.failedExpectations.length > 0 || (failSpecWithNoExpectations && this.result.failedExpectations.length + this.result.passedExpectations.length === 0) ) { return 'failed'; } return 'passed'; }; Spec.prototype.getFullName = function() { return this.getSpecName(this); }; Spec.prototype.addDeprecationWarning = function(deprecation) { if (typeof deprecation === 'string') { deprecation = { message: deprecation }; } this.result.deprecationWarnings.push( this.expectationResultFactory(deprecation) ); }; var extractCustomPendingMessage = function(e) { var fullMessage = e.toString(), boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; return fullMessage.substr(boilerplateEnd); }; Spec.pendingSpecExceptionMessage = '=> marked Pending'; Spec.isPendingSpecException = function(e) { return !!( e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1 ); }; return Spec; }; if (typeof window == void 0 && typeof exports == 'object') { /* globals exports */ exports.Spec = jasmineRequire.Spec; } /*jshint bitwise: false*/ getJasmineRequireObj().Order = function() { function Order(options) { this.random = 'random' in options ? options.random : true; var seed = (this.seed = options.seed || generateSeed()); this.sort = this.random ? randomOrder : naturalOrder; function naturalOrder(items) { return items; } function randomOrder(items) { var copy = items.slice(); copy.sort(function(a, b) { return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id); }); return copy; } function generateSeed() { return String(Math.random()).slice(-5); } // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function // used to get a different output when the key changes slightly. // We use your return to sort the children randomly in a consistent way when // used in conjunction with a seed function jenkinsHash(key) { var hash, i; for (hash = i = 0; i < key.length; ++i) { hash += key.charCodeAt(i); hash += hash << 10; hash ^= hash >> 6; } hash += hash << 3; hash ^= hash >> 11; hash += hash << 15; return hash; } } return Order; }; getJasmineRequireObj().Env = function(j$) { /** * _Note:_ Do not construct this directly, Jasmine will make one during booting. * @name Env * @since 2.0.0 * @classdesc The Jasmine environment * @constructor */ function Env(options) { options = options || {}; var self = this; var global = options.global || j$.getGlobal(); var customPromise; var totalSpecsDefined = 0; var realSetTimeout = global.setTimeout; var realClearTimeout = global.clearTimeout; var clearStack = j$.getClearStack(global); this.clock = new j$.Clock( global, function() { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global) ); var runnableResources = {}; var currentSpec = null; var currentlyExecutingSuites = []; var currentDeclarationSuite = null; var hasFailures = false; /** * This represents the available options to configure Jasmine. * Options that are not provided will use their default values * @interface Configuration * @since 3.3.0 */ var config = { /** * Whether to randomize spec execution order * @name Configuration#random * @since 3.3.0 * @type Boolean * @default false */ random: false, /** * Seed to use as the basis of randomization. * Null causes the seed to be determined randomly at the start of execution. * @name Configuration#seed * @since 3.3.0 * @type function * @default null */ seed: null, /** * Whether to stop execution of the suite after the first spec failure * @name Configuration#failFast * @since 3.3.0 * @type Boolean * @default false */ failFast: false, /** * Whether to fail the spec if it ran no expectations. By default * a spec that ran no expectations is reported as passed. Setting this * to true will report such spec as a failure. * @name Configuration#failSpecWithNoExpectations * @since 3.5.0 * @type Boolean * @default false */ failSpecWithNoExpectations: false, /** * Whether to cause specs to only have one expectation failure. * @name Configuration#oneFailurePerSpec * @since 3.3.0 * @type Boolean * @default false */ oneFailurePerSpec: false, /** * Function to use to filter specs * @name Configuration#specFilter * @since 3.3.0 * @type function * @default true */ specFilter: function() { return true; }, /** * Whether or not reporters should hide disabled specs from their output. * Currently only supported by Jasmine's HTMLReporter * @name Configuration#hideDisabled * @since 3.3.0 * @type Boolean * @default false */ hideDisabled: false, /** * Set to provide a custom promise library that Jasmine will use if it needs * to create a promise. If not set, it will default to whatever global Promise * library is available (if any). * @name Configuration#Promise * @since 3.5.0 * @type function * @default undefined */ Promise: undefined }; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1] || undefined; }; var currentRunnable = function() { return currentSpec || currentSuite(); }; var globalErrors = null; var installGlobalErrors = function() { if (globalErrors) { return; } globalErrors = new j$.GlobalErrors(); globalErrors.install(); }; if (!options.suppressLoadErrors) { installGlobalErrors(); globalErrors.pushListener(function( message, filename, lineno, colNo, err ) { topSuite.result.failedExpectations.push({ passed: false, globalErrorType: 'load', message: message, stack: err && err.stack, filename: filename, lineno: lineno }); }); } /** * Configure your jasmine environment * @name Env#configure * @since 3.3.0 * @argument {Configuration} configuration * @function */ this.configure = function(configuration) { if (configuration.specFilter) { config.specFilter = configuration.specFilter; } if (configuration.hasOwnProperty('random')) { config.random = !!configuration.random; } if (configuration.hasOwnProperty('seed')) { config.seed = configuration.seed; } if (configuration.hasOwnProperty('failFast')) { config.failFast = configuration.failFast; } if (configuration.hasOwnProperty('failSpecWithNoExpectations')) { config.failSpecWithNoExpectations = configuration.failSpecWithNoExpectations; } if (configuration.hasOwnProperty('oneFailurePerSpec')) { config.oneFailurePerSpec = configuration.oneFailurePerSpec; } if (configuration.hasOwnProperty('hideDisabled')) { config.hideDisabled = configuration.hideDisabled; } // Don't use hasOwnProperty to check for Promise existence because Promise // can be initialized to undefined, either explicitly or by using the // object returned from Env#configuration. In particular, Karma does this. if (configuration.Promise) { if ( typeof configuration.Promise.resolve === 'function' && typeof configuration.Promise.reject === 'function' ) { customPromise = configuration.Promise; } else { throw new Error( 'Custom promise library missing `resolve`/`reject` functions' ); } } }; /** * Get the current configuration for your jasmine environment * @name Env#configuration * @since 3.3.0 * @function * @returns {Configuration} */ this.configuration = function() { var result = {}; for (var property in config) { result[property] = config[property]; } return result; }; Object.defineProperty(this, 'specFilter', { get: function() { self.deprecated( 'Getting specFilter directly from Env is deprecated and will be removed in a future version of Jasmine, please check the specFilter option from `configuration`' ); return config.specFilter; }, set: function(val) { self.deprecated( 'Setting specFilter directly on Env is deprecated and will be removed in a future version of Jasmine, please use the specFilter option in `configure`' ); config.specFilter = val; } }); this.setDefaultSpyStrategy = function(defaultStrategyFn) { if (!currentRunnable()) { throw new Error( 'Default spy strategy must be set in a before function or a spec' ); } runnableResources[ currentRunnable().id ].defaultStrategyFn = defaultStrategyFn; }; this.addSpyStrategy = function(name, fn) { if (!currentRunnable()) { throw new Error( 'Custom spy strategies must be added in a before function or a spec' ); } runnableResources[currentRunnable().id].customSpyStrategies[name] = fn; }; this.addCustomEqualityTester = function(tester) { if (!currentRunnable()) { throw new Error( 'Custom Equalities must be added in a before function or a spec' ); } runnableResources[currentRunnable().id].customEqualityTesters.push( tester ); }; this.addMatchers = function(matchersToAdd) { if (!currentRunnable()) { throw new Error( 'Matchers must be added in a before function or a spec' ); } var customMatchers = runnableResources[currentRunnable().id].customMatchers; for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; } }; this.addAsyncMatchers = function(matchersToAdd) { if (!currentRunnable()) { throw new Error( 'Async Matchers must be added in a before function or a spec' ); } var customAsyncMatchers = runnableResources[currentRunnable().id].customAsyncMatchers; for (var matcherName in matchersToAdd) { customAsyncMatchers[matcherName] = matchersToAdd[matcherName]; } }; this.addCustomObjectFormatter = function(formatter) { if (!currentRunnable()) { throw new Error( 'Custom object formatters must be added in a before function or a spec' ); } runnableResources[currentRunnable().id].customObjectFormatters.push( formatter ); }; j$.Expectation.addCoreMatchers(j$.matchers); j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers); var nextSpecId = 0; var getNextSpecId = function() { return 'spec' + nextSpecId++; }; var nextSuiteId = 0; var getNextSuiteId = function() { return 'suite' + nextSuiteId++; }; var makePrettyPrinter = function() { var customObjectFormatters = runnableResources[currentRunnable().id].customObjectFormatters; return j$.makePrettyPrinter(customObjectFormatters); }; var makeMatchersUtil = function() { var customEqualityTesters = runnableResources[currentRunnable().id].customEqualityTesters; return new j$.MatchersUtil({ customTesters: customEqualityTesters, pp: makePrettyPrinter() }); }; var expectationFactory = function(actual, spec) { var customEqualityTesters = runnableResources[spec.id].customEqualityTesters; return j$.Expectation.factory({ matchersUtil: makeMatchersUtil(), customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult }); function addExpectationResult(passed, result) { return spec.addExpectationResult(passed, result); } }; function recordLateExpectation(runable, runableType, result) { var delayedExpectationResult = {}; Object.keys(result).forEach(function(k) { delayedExpectationResult[k] = result[k]; }); delayedExpectationResult.passed = false; delayedExpectationResult.globalErrorType = 'lateExpectation'; delayedExpectationResult.message = runableType + ' "' + runable.getFullName() + '" ran a "' + result.matcherName + '" expectation after it finished.\n'; if (result.message) { delayedExpectationResult.message += 'Message: "' + result.message + '"\n'; } delayedExpectationResult.message += 'Did you forget to return or await the result of expectAsync?'; topSuite.result.failedExpectations.push(delayedExpectationResult); } var asyncExpectationFactory = function(actual, spec, runableType) { return j$.Expectation.asyncFactory({ matchersUtil: makeMatchersUtil(), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, addExpectationResult: addExpectationResult }); function addExpectationResult(passed, result) { if (currentRunnable() !== spec) { recordLateExpectation(spec, runableType, result); } return spec.addExpectationResult(passed, result); } }; var suiteAsyncExpectationFactory = function(actual, suite) { return asyncExpectationFactory(actual, suite, 'Suite'); }; var specAsyncExpectationFactory = function(actual, suite) { return asyncExpectationFactory(actual, suite, 'Spec'); }; var defaultResourcesForRunnable = function(id, parentRunnableId) { var resources = { spies: [], customEqualityTesters: [], customMatchers: {}, customAsyncMatchers: {}, customSpyStrategies: {}, defaultStrategyFn: undefined, customObjectFormatters: [] }; if (runnableResources[parentRunnableId]) { resources.customEqualityTesters = j$.util.clone( runnableResources[parentRunnableId].customEqualityTesters ); resources.customMatchers = j$.util.clone( runnableResources[parentRunnableId].customMatchers ); resources.customAsyncMatchers = j$.util.clone( runnableResources[parentRunnableId].customAsyncMatchers ); resources.defaultStrategyFn = runnableResources[parentRunnableId].defaultStrategyFn; } runnableResources[id] = resources; }; var clearResourcesForRunnable = function(id) { spyRegistry.clearSpies(); delete runnableResources[id]; }; var beforeAndAfterFns = function(suite) { return function() { var befores = [], afters = []; while (suite) { befores = befores.concat(suite.beforeFns); afters = afters.concat(suite.afterFns); suite = suite.parentSuite; } return { befores: befores.reverse(), afters: afters }; }; }; var getSpecName = function(spec, suite) { var fullName = [spec.description], suiteFullName = suite.getFullName(); if (suiteFullName !== '') { fullName.unshift(suiteFullName); } return fullName.join(' '); }; // TODO: we may just be able to pass in the fn instead of wrapping here var buildExpectationResult = j$.buildExpectationResult, exceptionFormatter = new j$.ExceptionFormatter(), expectationResultFactory = function(attrs) { attrs.messageFormatter = exceptionFormatter.message; attrs.stackFormatter = exceptionFormatter.stack; return buildExpectationResult(attrs); }; /** * Sets whether Jasmine should throw an Error when an expectation fails. * This causes a spec to only have one expectation failure. * @name Env#throwOnExpectationFailure * @since 2.3.0 * @function * @param {Boolean} value Whether to throw when a expectation fails * @deprecated Use the `oneFailurePerSpec` option with {@link Env#configure} */ this.throwOnExpectationFailure = function(value) { this.deprecated( 'Setting throwOnExpectationFailure directly on Env is deprecated and will be removed in a future version of Jasmine, please use the oneFailurePerSpec option in `configure`' ); this.configure({ oneFailurePerSpec: !!value }); }; this.throwingExpectationFailures = function() { this.deprecated( 'Getting throwingExpectationFailures directly from Env is deprecated and will be removed in a future version of Jasmine, please check the oneFailurePerSpec option from `configuration`' ); return config.oneFailurePerSpec; }; /** * Set whether to stop suite execution when a spec fails * @name Env#stopOnSpecFailure * @since 2.7.0 * @function * @param {Boolean} value Whether to stop suite execution when a spec fails * @deprecated Use the `failFast` option with {@link Env#configure} */ this.stopOnSpecFailure = function(value) { this.deprecated( 'Setting stopOnSpecFailure directly is deprecated and will be removed in a future version of Jasmine, please use the failFast option in `configure`' ); this.configure({ failFast: !!value }); }; this.stoppingOnSpecFailure = function() { this.deprecated( 'Getting stoppingOnSpecFailure directly from Env is deprecated and will be removed in a future version of Jasmine, please check the failFast option from `configuration`' ); return config.failFast; }; /** * Set whether to randomize test execution order * @name Env#randomizeTests * @since 2.4.0 * @function * @param {Boolean} value Whether to randomize execution order * @deprecated Use the `random` option with {@link Env#configure} */ this.randomizeTests = function(value) { this.deprecated( 'Setting randomizeTests directly is deprecated and will be removed in a future version of Jasmine, please use the random option in `configure`' ); config.random = !!value; }; this.randomTests = function() { this.deprecated( 'Getting randomTests directly from Env is deprecated and will be removed in a future version of Jasmine, please check the random option from `configuration`' ); return config.random; }; /** * Set the random number seed for spec randomization * @name Env#seed * @since 2.4.0 * @function * @param {Number} value The seed value * @deprecated Use the `seed` option with {@link Env#configure} */ this.seed = function(value) { this.deprecated( 'Setting seed directly is deprecated and will be removed in a future version of Jasmine, please use the seed option in `configure`' ); if (value) { config.seed = value; } return config.seed; }; this.hidingDisabled = function(value) { this.deprecated( 'Getting hidingDisabled directly from Env is deprecated and will be removed in a future version of Jasmine, please check the hideDisabled option from `configuration`' ); return config.hideDisabled; }; /** * @name Env#hideDisabled * @since 3.2.0 * @function */ this.hideDisabled = function(value) { this.deprecated( 'Setting hideDisabled directly is deprecated and will be removed in a future version of Jasmine, please use the hideDisabled option in `configure`' ); config.hideDisabled = !!value; }; this.deprecated = function(deprecation) { var runnable = currentRunnable() || topSuite; runnable.addDeprecationWarning(deprecation); if ( typeof console !== 'undefined' && typeof console.error === 'function' ) { console.error('DEPRECATION:', deprecation); } }; var queueRunnerFactory = function(options, args) { var failFast = false; if (options.isLeaf) { failFast = config.oneFailurePerSpec; } else if (!options.isReporter) { failFast = config.failFast; } options.clearStack = options.clearStack || clearStack; options.timeout = { setTimeout: realSetTimeout, clearTimeout: realClearTimeout }; options.fail = self.fail; options.globalErrors = globalErrors; options.completeOnFirstError = failFast; options.onException = options.onException || function(e) { (currentRunnable() || topSuite).onException(e); }; options.deprecated = self.deprecated; new j$.QueueRunner(options).execute(args); }; var topSuite = new j$.Suite({ env: this, id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', expectationFactory: expectationFactory, asyncExpectationFactory: suiteAsyncExpectationFactory, expectationResultFactory: expectationResultFactory }); defaultResourcesForRunnable(topSuite.id); currentDeclarationSuite = topSuite; this.topSuite = function() { return topSuite; }; /** * This represents the available reporter callback for an object passed to {@link Env#addReporter}. * @interface Reporter * @see custom_reporter */ var reporter = new j$.ReportDispatcher( [ /** * `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts. * @function * @name Reporter#jasmineStarted * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'jasmineStarted', /** * When the entire suite has finished execution `jasmineDone` is called * @function * @name Reporter#jasmineDone * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running. * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'jasmineDone', /** * `suiteStarted` is invoked when a `describe` starts to run * @function * @name Reporter#suiteStarted * @param {SuiteResult} result Information about the individual {@link describe} being run * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'suiteStarted', /** * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run * * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`. * @function * @name Reporter#suiteDone * @param {SuiteResult} result * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'suiteDone', /** * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) * @function * @name Reporter#specStarted * @param {SpecResult} result Information about the individual {@link it} being run * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'specStarted', /** * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run. * * While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed. * @function * @name Reporter#specDone * @param {SpecResult} result * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'specDone' ], queueRunnerFactory ); this.execute = function(runnablesToRun) { installGlobalErrors(); if (!runnablesToRun) { if (focusedRunnables.length) { runnablesToRun = focusedRunnables; } else { runnablesToRun = [topSuite.id]; } } var order = new j$.Order({ random: config.random, seed: config.seed }); var processor = new j$.TreeProcessor({ tree: topSuite, runnableIds: runnablesToRun, queueRunnerFactory: queueRunnerFactory, failSpecWithNoExpectations: config.failSpecWithNoExpectations, nodeStart: function(suite, next) { currentlyExecutingSuites.push(suite); defaultResourcesForRunnable(suite.id, suite.parentSuite.id); reporter.suiteStarted(suite.result, next); suite.startTimer(); }, nodeComplete: function(suite, result, next) { if (suite !== currentSuite()) { throw new Error('Tried to complete the wrong suite'); } clearResourcesForRunnable(suite.id); currentlyExecutingSuites.pop(); if (result.status === 'failed') { hasFailures = true; } suite.endTimer(); reporter.suiteDone(result, next); }, orderChildren: function(node) { return order.sort(node.children); }, excludeNode: function(spec) { return !config.specFilter(spec); } }); if (!processor.processTree().valid) { throw new Error( 'Invalid order: would cause a beforeAll or afterAll to be run multiple times' ); } var jasmineTimer = new j$.Timer(); jasmineTimer.start(); /** * Information passed to the {@link Reporter#jasmineStarted} event. * @typedef JasmineStartedInfo * @property {Int} totalSpecsDefined - The total number of specs defined in this suite. * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. */ reporter.jasmineStarted( { totalSpecsDefined: totalSpecsDefined, order: order }, function() { currentlyExecutingSuites.push(topSuite); processor.execute(function() { clearResourcesForRunnable(topSuite.id); currentlyExecutingSuites.pop(); var overallStatus, incompleteReason; if (hasFailures || topSuite.result.failedExpectations.length > 0) { overallStatus = 'failed'; } else if (focusedRunnables.length > 0) { overallStatus = 'incomplete'; incompleteReason = 'fit() or fdescribe() was found'; } else if (totalSpecsDefined === 0) { overallStatus = 'incomplete'; incompleteReason = 'No specs found'; } else { overallStatus = 'passed'; } /** * Information passed to the {@link Reporter#jasmineDone} event. * @typedef JasmineDoneInfo * @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'. * @property {Int} totalTime - The total time (in ms) that it took to execute the suite * @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete. * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. * @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level. */ reporter.jasmineDone( { overallStatus: overallStatus, totalTime: jasmineTimer.elapsed(), incompleteReason: incompleteReason, order: order, failedExpectations: topSuite.result.failedExpectations, deprecationWarnings: topSuite.result.deprecationWarnings }, function() {} ); }); } ); }; /** * Add a custom reporter to the Jasmine environment. * @name Env#addReporter * @since 2.0.0 * @function * @param {Reporter} reporterToAdd The reporter to be added. * @see custom_reporter */ this.addReporter = function(reporterToAdd) { reporter.addReporter(reporterToAdd); }; /** * Provide a fallback reporter if no other reporters have been specified. * @name Env#provideFallbackReporter * @since 2.5.0 * @function * @param {Reporter} reporterToAdd The reporter * @see custom_reporter */ this.provideFallbackReporter = function(reporterToAdd) { reporter.provideFallbackReporter(reporterToAdd); }; /** * Clear all registered reporters * @name Env#clearReporters * @since 2.5.2 * @function */ this.clearReporters = function() { reporter.clearReporters(); }; var spyFactory = new j$.SpyFactory( function getCustomStrategies() { var runnable = currentRunnable(); if (runnable) { return runnableResources[runnable.id].customSpyStrategies; } return {}; }, function getDefaultStrategyFn() { var runnable = currentRunnable(); if (runnable) { return runnableResources[runnable.id].defaultStrategyFn; } return undefined; }, function getPromise() { return customPromise || global.Promise; } ); var spyRegistry = new j$.SpyRegistry({ currentSpies: function() { if (!currentRunnable()) { throw new Error( 'Spies must be created in a before function or a spec' ); } return runnableResources[currentRunnable().id].spies; }, createSpy: function(name, originalFn) { return self.createSpy(name, originalFn); } }); this.allowRespy = function(allow) { spyRegistry.allowRespy(allow); }; this.spyOn = function() { return spyRegistry.spyOn.apply(spyRegistry, arguments); }; this.spyOnProperty = function() { return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); }; this.spyOnAllFunctions = function() { return spyRegistry.spyOnAllFunctions.apply(spyRegistry, arguments); }; this.createSpy = function(name, originalFn) { if (arguments.length === 1 && j$.isFunction_(name)) { originalFn = name; name = originalFn.name; } return spyFactory.createSpy(name, originalFn); }; this.createSpyObj = function(baseName, methodNames, propertyNames) { return spyFactory.createSpyObj(baseName, methodNames, propertyNames); }; var ensureIsFunction = function(fn, caller) { if (!j$.isFunction_(fn)) { throw new Error( caller + ' expects a function argument; received ' + j$.getType_(fn) ); } }; var ensureIsFunctionOrAsync = function(fn, caller) { if (!j$.isFunction_(fn) && !j$.isAsyncFunction_(fn)) { throw new Error( caller + ' expects a function argument; received ' + j$.getType_(fn) ); } }; function ensureIsNotNested(method) { var runnable = currentRunnable(); if (runnable !== null && runnable !== undefined) { throw new Error( "'" + method + "' should only be used in 'describe' function" ); } } var suiteFactory = function(description) { var suite = new j$.Suite({ env: self, id: getNextSuiteId(), description: description, parentSuite: currentDeclarationSuite, timer: new j$.Timer(), expectationFactory: expectationFactory, asyncExpectationFactory: suiteAsyncExpectationFactory, expectationResultFactory: expectationResultFactory, throwOnExpectationFailure: config.oneFailurePerSpec }); return suite; }; this.describe = function(description, specDefinitions) { ensureIsNotNested('describe'); ensureIsFunction(specDefinitions, 'describe'); var suite = suiteFactory(description); if (specDefinitions.length > 0) { throw new Error('describe does not expect any arguments'); } if (currentDeclarationSuite.markedPending) { suite.pend(); } addSpecsToSuite(suite, specDefinitions); return suite; }; this.xdescribe = function(description, specDefinitions) { ensureIsNotNested('xdescribe'); ensureIsFunction(specDefinitions, 'xdescribe'); var suite = suiteFactory(description); suite.pend(); addSpecsToSuite(suite, specDefinitions); return suite; }; var focusedRunnables = []; this.fdescribe = function(description, specDefinitions) { ensureIsNotNested('fdescribe'); ensureIsFunction(specDefinitions, 'fdescribe'); var suite = suiteFactory(description); suite.isFocused = true; focusedRunnables.push(suite.id); unfocusAncestor(); addSpecsToSuite(suite, specDefinitions); return suite; }; function addSpecsToSuite(suite, specDefinitions) { var parentSuite = currentDeclarationSuite; parentSuite.addChild(suite); currentDeclarationSuite = suite; var declarationError = null; try { specDefinitions.call(suite); } catch (e) { declarationError = e; } if (declarationError) { suite.onException(declarationError); } currentDeclarationSuite = parentSuite; } function findFocusedAncestor(suite) { while (suite) { if (suite.isFocused) { return suite.id; } suite = suite.parentSuite; } return null; } function unfocusAncestor() { var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); if (focusedAncestor) { for (var i = 0; i < focusedRunnables.length; i++) { if (focusedRunnables[i] === focusedAncestor) { focusedRunnables.splice(i, 1); break; } } } } var specFactory = function(description, fn, suite, timeout) { totalSpecsDefined++; var spec = new j$.Spec({ id: getNextSpecId(), beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: expectationFactory, asyncExpectationFactory: specAsyncExpectationFactory, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); }, onStart: specStarted, description: description, expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, userContext: function() { return suite.clonedSharedUserContext(); }, queueableFn: { fn: fn, timeout: timeout || 0 }, throwOnExpectationFailure: config.oneFailurePerSpec, timer: new j$.Timer() }); return spec; function specResultCallback(result, next) { clearResourcesForRunnable(spec.id); currentSpec = null; if (result.status === 'failed') { hasFailures = true; } reporter.specDone(result, next); } function specStarted(spec, next) { currentSpec = spec; defaultResourcesForRunnable(spec.id, suite.id); reporter.specStarted(spec.result, next); } }; this.it = function(description, fn, timeout) { ensureIsNotNested('it'); // it() sometimes doesn't have a fn argument, so only check the type if // it's given. if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'it'); } var spec = specFactory(description, fn, currentDeclarationSuite, timeout); if (currentDeclarationSuite.markedPending) { spec.pend(); } currentDeclarationSuite.addChild(spec); return spec; }; this.xit = function(description, fn, timeout) { ensureIsNotNested('xit'); // xit(), like it(), doesn't always have a fn argument, so only check the // type when needed. if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'xit'); } var spec = this.it.apply(this, arguments); spec.pend('Temporarily disabled with xit'); return spec; }; this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureIsFunctionOrAsync(fn, 'fit'); var spec = specFactory(description, fn, currentDeclarationSuite, timeout); currentDeclarationSuite.addChild(spec); focusedRunnables.push(spec.id); unfocusAncestor(); return spec; }; /** * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SpecResult} * @name Env#setSpecProperty * @since 3.6.0 * @function * @param {String} key The name of the property * @param {*} value The value of the property */ this.setSpecProperty = function(key, value) { if (!currentRunnable() || currentRunnable() == currentSuite()) { throw new Error( "'setSpecProperty' was used when there was no current spec" ); } currentRunnable().setSpecProperty(key, value); }; /** * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SuiteResult} * @name Env#setSuiteProperty * @since 3.6.0 * @function * @param {String} key The name of the property * @param {*} value The value of the property */ this.setSuiteProperty = function(key, value) { if (!currentSuite()) { throw new Error( "'setSuiteProperty' was used when there was no current suite" ); } currentSuite().setSuiteProperty(key, value); }; this.expect = function(actual) { if (!currentRunnable()) { throw new Error( "'expect' was used when there was no current spec, this could be because an asynchronous test timed out" ); } return currentRunnable().expect(actual); }; this.expectAsync = function(actual) { if (!currentRunnable()) { throw new Error( "'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out" ); } return currentRunnable().expectAsync(actual); }; this.beforeEach = function(beforeEachFunction, timeout) { ensureIsNotNested('beforeEach'); ensureIsFunctionOrAsync(beforeEachFunction, 'beforeEach'); currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: timeout || 0 }); }; this.beforeAll = function(beforeAllFunction, timeout) { ensureIsNotNested('beforeAll'); ensureIsFunctionOrAsync(beforeAllFunction, 'beforeAll'); currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: timeout || 0 }); }; this.afterEach = function(afterEachFunction, timeout) { ensureIsNotNested('afterEach'); ensureIsFunctionOrAsync(afterEachFunction, 'afterEach'); afterEachFunction.isCleanup = true; currentDeclarationSuite.afterEach({ fn: afterEachFunction, timeout: timeout || 0 }); }; this.afterAll = function(afterAllFunction, timeout) { ensureIsNotNested('afterAll'); ensureIsFunctionOrAsync(afterAllFunction, 'afterAll'); currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: timeout || 0 }); }; this.pending = function(message) { var fullMessage = j$.Spec.pendingSpecExceptionMessage; if (message) { fullMessage += message; } throw fullMessage; }; this.fail = function(error) { if (!currentRunnable()) { throw new Error( "'fail' was used when there was no current spec, this could be because an asynchronous test timed out" ); } var message = 'Failed'; if (error) { message += ': '; if (error.message) { message += error.message; } else if (j$.isString_(error)) { message += error; } else { // pretty print all kind of objects. This includes arrays. message += makePrettyPrinter()(error); } } currentRunnable().addExpectationResult(false, { matcherName: '', passed: false, expected: '', actual: '', message: message, error: error && error.message ? error : null }); if (config.oneFailurePerSpec) { throw new Error(message); } }; this.cleanup_ = function() { if (globalErrors) { globalErrors.uninstall(); } }; } return Env; }; getJasmineRequireObj().JsApiReporter = function(j$) { /** * @name jsApiReporter * @classdesc {@link Reporter} added by default in `boot.js` to record results for retrieval in javascript code. An instance is made available as `jsApiReporter` on the global object. * @class * @hideconstructor */ function JsApiReporter(options) { var timer = options.timer || new j$.Timer(), status = 'loaded'; this.started = false; this.finished = false; this.runDetails = {}; this.jasmineStarted = function() { this.started = true; status = 'started'; timer.start(); }; var executionTime; this.jasmineDone = function(runDetails) { this.finished = true; this.runDetails = runDetails; executionTime = timer.elapsed(); status = 'done'; }; /** * Get the current status for the Jasmine environment. * @name jsApiReporter#status * @since 2.0.0 * @function * @return {String} - One of `loaded`, `started`, or `done` */ this.status = function() { return status; }; var suites = [], suites_hash = {}; this.suiteStarted = function(result) { suites_hash[result.id] = result; }; this.suiteDone = function(result) { storeSuite(result); }; /** * Get the results for a set of suites. * * Retrievable in slices for easier serialization. * @name jsApiReporter#suiteResults * @since 2.1.0 * @function * @param {Number} index - The position in the suites list to start from. * @param {Number} length - Maximum number of suite results to return. * @return {SuiteResult[]} */ this.suiteResults = function(index, length) { return suites.slice(index, index + length); }; function storeSuite(result) { suites.push(result); suites_hash[result.id] = result; } /** * Get all of the suites in a single object, with their `id` as the key. * @name jsApiReporter#suites * @since 2.0.0 * @function * @return {Object} - Map of suite id to {@link SuiteResult} */ this.suites = function() { return suites_hash; }; var specs = []; this.specDone = function(result) { specs.push(result); }; /** * Get the results for a set of specs. * * Retrievable in slices for easier serialization. * @name jsApiReporter#specResults * @since 2.0.0 * @function * @param {Number} index - The position in the specs list to start from. * @param {Number} length - Maximum number of specs results to return. * @return {SpecResult[]} */ this.specResults = function(index, length) { return specs.slice(index, index + length); }; /** * Get all spec results. * @name jsApiReporter#specs * @since 2.0.0 * @function * @return {SpecResult[]} */ this.specs = function() { return specs; }; /** * Get the number of milliseconds it took for the full Jasmine suite to run. * @name jsApiReporter#executionTime * @since 2.0.0 * @function * @return {Number} */ this.executionTime = function() { return executionTime; }; } return JsApiReporter; }; getJasmineRequireObj().Any = function(j$) { function Any(expectedObject) { if (typeof expectedObject === 'undefined') { throw new TypeError( 'jasmine.any() expects to be passed a constructor function. ' + 'Please pass one or use jasmine.anything() to match any object.' ); } this.expectedObject = expectedObject; } Any.prototype.asymmetricMatch = function(other) { if (this.expectedObject == String) { return typeof other == 'string' || other instanceof String; } if (this.expectedObject == Number) { return typeof other == 'number' || other instanceof Number; } if (this.expectedObject == Function) { return typeof other == 'function' || other instanceof Function; } if (this.expectedObject == Object) { return other !== null && typeof other == 'object'; } if (this.expectedObject == Boolean) { return typeof other == 'boolean'; } /* jshint -W122 */ /* global Symbol */ if (typeof Symbol != 'undefined' && this.expectedObject == Symbol) { return typeof other == 'symbol'; } /* jshint +W122 */ return other instanceof this.expectedObject; }; Any.prototype.jasmineToString = function() { return ''; }; return Any; }; getJasmineRequireObj().Anything = function(j$) { function Anything() {} Anything.prototype.asymmetricMatch = function(other) { return !j$.util.isUndefined(other) && other !== null; }; Anything.prototype.jasmineToString = function() { return ''; }; return Anything; }; getJasmineRequireObj().ArrayContaining = function(j$) { function ArrayContaining(sample) { this.sample = sample; } ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayContaining, not ' + j$.pp(this.sample) + '.'); } // If the actual parameter is not an array, we can fail immediately, since it couldn't // possibly be an "array containing" anything. However, we also want an empty sample // array to match anything, so we need to double-check we aren't in that case if (!j$.isArray_(other) && this.sample.length > 0) { return false; } for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; if (!matchersUtil.contains(other, item)) { return false; } } return true; }; ArrayContaining.prototype.jasmineToString = function (pp) { return ''; }; return ArrayContaining; }; getJasmineRequireObj().ArrayWithExactContents = function(j$) { function ArrayWithExactContents(sample) { this.sample = sample; } ArrayWithExactContents.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayWithExactContents, not ' + j$.pp(this.sample) + '.'); } if (this.sample.length !== other.length) { return false; } for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; if (!matchersUtil.contains(other, item)) { return false; } } return true; }; ArrayWithExactContents.prototype.jasmineToString = function(pp) { return ''; }; return ArrayWithExactContents; }; getJasmineRequireObj().Empty = function (j$) { function Empty() {} Empty.prototype.asymmetricMatch = function (other) { if (j$.isString_(other) || j$.isArray_(other) || j$.isTypedArray_(other)) { return other.length === 0; } if (j$.isMap(other) || j$.isSet(other)) { return other.size === 0; } if (j$.isObject_(other)) { return Object.keys(other).length === 0; } return false; }; Empty.prototype.jasmineToString = function () { return ''; }; return Empty; }; getJasmineRequireObj().Falsy = function(j$) { function Falsy() {} Falsy.prototype.asymmetricMatch = function(other) { return !other; }; Falsy.prototype.jasmineToString = function() { return ''; }; return Falsy; }; getJasmineRequireObj().MapContaining = function(j$) { function MapContaining(sample) { if (!j$.isMap(sample)) { throw new Error('You must provide a map to `mapContaining`, not ' + j$.pp(sample)); } this.sample = sample; } MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isMap(other)) return false; var hasAllMatches = true; j$.util.forEachBreakable(this.sample, function(breakLoop, value, key) { // for each key/value pair in `sample` // there should be at least one pair in `other` whose key and value both match var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oValue, oKey) { if ( matchersUtil.equals(oKey, key) && matchersUtil.equals(oValue, value) ) { hasMatch = true; oBreakLoop(); } }); if (!hasMatch) { hasAllMatches = false; breakLoop(); } }); return hasAllMatches; }; MapContaining.prototype.jasmineToString = function(pp) { return ''; }; return MapContaining; }; getJasmineRequireObj().NotEmpty = function (j$) { function NotEmpty() {} NotEmpty.prototype.asymmetricMatch = function (other) { if (j$.isString_(other) || j$.isArray_(other) || j$.isTypedArray_(other)) { return other.length !== 0; } if (j$.isMap(other) || j$.isSet(other)) { return other.size !== 0; } if (j$.isObject_(other)) { return Object.keys(other).length !== 0; } return false; }; NotEmpty.prototype.jasmineToString = function () { return ''; }; return NotEmpty; }; getJasmineRequireObj().ObjectContaining = function(j$) { function ObjectContaining(sample) { this.sample = sample; } function getPrototype(obj) { if (Object.getPrototypeOf) { return Object.getPrototypeOf(obj); } if (obj.constructor.prototype == obj) { return null; } return obj.constructor.prototype; } function hasProperty(obj, property) { if (!obj || typeof(obj) !== 'object') { return false; } if (Object.prototype.hasOwnProperty.call(obj, property)) { return true; } return hasProperty(getPrototype(obj), property); } ObjectContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } if (typeof(other) !== 'object') { return false; } for (var property in this.sample) { if (!hasProperty(other, property) || !matchersUtil.equals(this.sample[property], other[property])) { return false; } } return true; }; ObjectContaining.prototype.valuesForDiff_ = function(other, pp) { if (!j$.isObject_(other)) { return { self: this.jasmineToString(pp), other: other }; } var filteredOther = {}; Object.keys(this.sample).forEach(function (k) { // eq short-circuits comparison of objects that have different key sets, // so include all keys even if undefined. filteredOther[k] = other[k]; }); return { self: this.sample, other: filteredOther }; }; ObjectContaining.prototype.jasmineToString = function(pp) { return ''; }; return ObjectContaining; }; getJasmineRequireObj().SetContaining = function(j$) { function SetContaining(sample) { if (!j$.isSet(sample)) { throw new Error('You must provide a set to `setContaining`, not ' + j$.pp(sample)); } this.sample = sample; } SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isSet(other)) return false; var hasAllMatches = true; j$.util.forEachBreakable(this.sample, function(breakLoop, item) { // for each item in `sample` there should be at least one matching item in `other` // (not using `matchersUtil.contains` because it compares set members by reference, // not by deep value equality) var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oItem) { if (matchersUtil.equals(oItem, item)) { hasMatch = true; oBreakLoop(); } }); if (!hasMatch) { hasAllMatches = false; breakLoop(); } }); return hasAllMatches; }; SetContaining.prototype.jasmineToString = function(pp) { return ''; }; return SetContaining; }; getJasmineRequireObj().StringMatching = function(j$) { function StringMatching(expected) { if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { throw new Error('Expected is not a String or a RegExp'); } this.regexp = new RegExp(expected); } StringMatching.prototype.asymmetricMatch = function(other) { return this.regexp.test(other); }; StringMatching.prototype.jasmineToString = function() { return ''; }; return StringMatching; }; getJasmineRequireObj().Truthy = function(j$) { function Truthy() {} Truthy.prototype.asymmetricMatch = function(other) { return !!other; }; Truthy.prototype.jasmineToString = function() { return ''; }; return Truthy; }; getJasmineRequireObj().asymmetricEqualityTesterArgCompatShim = function(j$) { /* Older versions of Jasmine passed an array of custom equality testers as the second argument to each asymmetric equality tester's `asymmetricMatch` method. Newer versions will pass a `MatchersUtil` instance. The asymmetricEqualityTesterArgCompatShim allows for a graceful migration from the old interface to the new by "being" both an array of custom equality testers and a `MatchersUtil` at the same time. This code should be removed in the next major release. */ var likelyArrayProps = [ 'concat', 'constructor', 'copyWithin', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'length', 'map', 'pop', 'push', 'reduce', 'reduceRight', 'reverse', 'shift', 'slice', 'some', 'sort', 'splice', 'toLocaleString', 'toSource', 'toString', 'unshift', 'values' ]; function asymmetricEqualityTesterArgCompatShim( matchersUtil, customEqualityTesters ) { var self = Object.create(matchersUtil), props, i, k; copy(self, customEqualityTesters, 'length'); for (i = 0; i < customEqualityTesters.length; i++) { copy(self, customEqualityTesters, i); } var props = arrayProps(); for (i = 0; i < props.length; i++) { k = props[i]; if (k !== 'length') { copy(self, Array.prototype, k); } } return self; } function copy(dest, src, propName) { Object.defineProperty(dest, propName, { get: function() { return src[propName]; } }); } function arrayProps() { var props, a, k; if (!Object.getOwnPropertyDescriptors) { return likelyArrayProps.filter(function(k) { return Array.prototype.hasOwnProperty(k); }); } props = Object.getOwnPropertyDescriptors(Array.prototype); // eslint-disable-line compat/compat a = []; for (k in props) { a.push(k); } return a; } return asymmetricEqualityTesterArgCompatShim; }; getJasmineRequireObj().CallTracker = function(j$) { /** * @namespace Spy#calls * @since 2.0.0 */ function CallTracker() { var calls = []; var opts = {}; this.track = function(context) { if (opts.cloneArgs) { context.args = j$.util.cloneArgs(context.args); } calls.push(context); }; /** * Check whether this spy has been invoked. * @name Spy#calls#any * @since 2.0.0 * @function * @return {Boolean} */ this.any = function() { return !!calls.length; }; /** * Get the number of invocations of this spy. * @name Spy#calls#count * @since 2.0.0 * @function * @return {Integer} */ this.count = function() { return calls.length; }; /** * Get the arguments that were passed to a specific invocation of this spy. * @name Spy#calls#argsFor * @since 2.0.0 * @function * @param {Integer} index The 0-based invocation index. * @return {Array} */ this.argsFor = function(index) { var call = calls[index]; return call ? call.args : []; }; /** * Get the raw calls array for this spy. * @name Spy#calls#all * @since 2.0.0 * @function * @return {Spy.callData[]} */ this.all = function() { return calls; }; /** * Get all of the arguments for each invocation of this spy in the order they were received. * @name Spy#calls#allArgs * @since 2.0.0 * @function * @return {Array} */ this.allArgs = function() { var callArgs = []; for (var i = 0; i < calls.length; i++) { callArgs.push(calls[i].args); } return callArgs; }; /** * Get the first invocation of this spy. * @name Spy#calls#first * @since 2.0.0 * @function * @return {ObjecSpy.callData} */ this.first = function() { return calls[0]; }; /** * Get the most recent invocation of this spy. * @name Spy#calls#mostRecent * @since 2.0.0 * @function * @return {ObjecSpy.callData} */ this.mostRecent = function() { return calls[calls.length - 1]; }; /** * Reset this spy as if it has never been called. * @name Spy#calls#reset * @since 2.0.0 * @function */ this.reset = function() { calls = []; }; /** * Set this spy to do a shallow clone of arguments passed to each invocation. * @name Spy#calls#saveArgumentsByValue * @since 2.5.0 * @function */ this.saveArgumentsByValue = function() { opts.cloneArgs = true; }; } return CallTracker; }; getJasmineRequireObj().clearStack = function(j$) { var maxInlineCallCount = 10; function messageChannelImpl(global, setTimeout) { var channel = new global.MessageChannel(), head = {}, tail = head; var taskRunning = false; channel.port1.onmessage = function() { head = head.next; var task = head.task; delete head.task; if (taskRunning) { global.setTimeout(task, 0); } else { try { taskRunning = true; task(); } finally { taskRunning = false; } } }; var currentCallCount = 0; return function clearStack(fn) { currentCallCount++; if (currentCallCount < maxInlineCallCount) { tail = tail.next = { task: fn }; channel.port2.postMessage(0); } else { currentCallCount = 0; setTimeout(fn); } }; } function getClearStack(global) { var currentCallCount = 0; var realSetTimeout = global.setTimeout; var setTimeoutImpl = function clearStack(fn) { Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]); }; if (global.setImmediate && j$.isFunction_(global.setImmediate)) { var realSetImmediate = global.setImmediate; return function(fn) { currentCallCount++; if (currentCallCount < maxInlineCallCount) { realSetImmediate(fn); } else { currentCallCount = 0; setTimeoutImpl(fn); } }; } else if (global.MessageChannel && !j$.util.isUndefined(global.MessageChannel)) { return messageChannelImpl(global, setTimeoutImpl); } else { return setTimeoutImpl; } } return getClearStack; }; getJasmineRequireObj().Clock = function() { /* global process */ var NODE_JS = typeof process !== 'undefined' && process.versions && typeof process.versions.node === 'string'; /** * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}. * @class Clock * @classdesc Jasmine's mock clock is used when testing time dependent code. */ function Clock(global, delayedFunctionSchedulerFactory, mockDate) { var self = this, realTimingFunctions = { setTimeout: global.setTimeout, clearTimeout: global.clearTimeout, setInterval: global.setInterval, clearInterval: global.clearInterval }, fakeTimingFunctions = { setTimeout: setTimeout, clearTimeout: clearTimeout, setInterval: setInterval, clearInterval: clearInterval }, installed = false, delayedFunctionScheduler, timer; self.FakeTimeout = FakeTimeout; /** * Install the mock clock over the built-in methods. * @name Clock#install * @since 2.0.0 * @function * @return {Clock} */ self.install = function() { if (!originalTimingFunctionsIntact()) { throw new Error( 'Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?' ); } replace(global, fakeTimingFunctions); timer = fakeTimingFunctions; delayedFunctionScheduler = delayedFunctionSchedulerFactory(); installed = true; return self; }; /** * Uninstall the mock clock, returning the built-in methods to their places. * @name Clock#uninstall * @since 2.0.0 * @function */ self.uninstall = function() { delayedFunctionScheduler = null; mockDate.uninstall(); replace(global, realTimingFunctions); timer = realTimingFunctions; installed = false; }; /** * Execute a function with a mocked Clock * * The clock will be {@link Clock#install|install}ed before the function is called and {@link Clock#uninstall|uninstall}ed in a `finally` after the function completes. * @name Clock#withMock * @since 2.3.0 * @function * @param {Function} closure The function to be called. */ self.withMock = function(closure) { this.install(); try { closure(); } finally { this.uninstall(); } }; /** * Instruct the installed Clock to also mock the date returned by `new Date()` * @name Clock#mockDate * @since 2.1.0 * @function * @param {Date} [initialDate=now] The `Date` to provide. */ self.mockDate = function(initialDate) { mockDate.install(initialDate); }; self.setTimeout = function(fn, delay, params) { return Function.prototype.apply.apply(timer.setTimeout, [ global, arguments ]); }; self.setInterval = function(fn, delay, params) { return Function.prototype.apply.apply(timer.setInterval, [ global, arguments ]); }; self.clearTimeout = function(id) { return Function.prototype.call.apply(timer.clearTimeout, [global, id]); }; self.clearInterval = function(id) { return Function.prototype.call.apply(timer.clearInterval, [global, id]); }; /** * Tick the Clock forward, running any enqueued timeouts along the way * @name Clock#tick * @since 1.3.0 * @function * @param {int} millis The number of milliseconds to tick. */ self.tick = function(millis) { if (installed) { delayedFunctionScheduler.tick(millis, function(millis) { mockDate.tick(millis); }); } else { throw new Error( 'Mock clock is not installed, use jasmine.clock().install()' ); } }; return self; function originalTimingFunctionsIntact() { return ( global.setTimeout === realTimingFunctions.setTimeout && global.clearTimeout === realTimingFunctions.clearTimeout && global.setInterval === realTimingFunctions.setInterval && global.clearInterval === realTimingFunctions.clearInterval ); } function replace(dest, source) { for (var prop in source) { dest[prop] = source[prop]; } } function setTimeout(fn, delay) { if (!NODE_JS) { return delayedFunctionScheduler.scheduleFunction( fn, delay, argSlice(arguments, 2) ); } var timeout = new FakeTimeout(); delayedFunctionScheduler.scheduleFunction( fn, delay, argSlice(arguments, 2), false, timeout ); return timeout; } function clearTimeout(id) { return delayedFunctionScheduler.removeFunctionWithId(id); } function setInterval(fn, interval) { if (!NODE_JS) { return delayedFunctionScheduler.scheduleFunction( fn, interval, argSlice(arguments, 2), true ); } var timeout = new FakeTimeout(); delayedFunctionScheduler.scheduleFunction( fn, interval, argSlice(arguments, 2), true, timeout ); return timeout; } function clearInterval(id) { return delayedFunctionScheduler.removeFunctionWithId(id); } function argSlice(argsObj, n) { return Array.prototype.slice.call(argsObj, n); } } /** * Mocks Node.js Timeout class */ function FakeTimeout() {} FakeTimeout.prototype.ref = function() { return this; }; FakeTimeout.prototype.unref = function() { return this; }; return Clock; }; getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { function DelayedFunctionScheduler() { var self = this; var scheduledLookup = []; var scheduledFunctions = {}; var currentTime = 0; var delayedFnCount = 0; var deletedKeys = []; self.tick = function(millis, tickDate) { millis = millis || 0; var endTime = currentTime + millis; runScheduledFunctions(endTime, tickDate); currentTime = endTime; }; self.scheduleFunction = function( funcToCall, millis, params, recurring, timeoutKey, runAtMillis ) { var f; if (typeof funcToCall === 'string') { /* jshint evil: true */ f = function() { return eval(funcToCall); }; /* jshint evil: false */ } else { f = funcToCall; } millis = millis || 0; timeoutKey = timeoutKey || ++delayedFnCount; runAtMillis = runAtMillis || currentTime + millis; var funcToSchedule = { runAtMillis: runAtMillis, funcToCall: f, recurring: recurring, params: params, timeoutKey: timeoutKey, millis: millis }; if (runAtMillis in scheduledFunctions) { scheduledFunctions[runAtMillis].push(funcToSchedule); } else { scheduledFunctions[runAtMillis] = [funcToSchedule]; scheduledLookup.push(runAtMillis); scheduledLookup.sort(function(a, b) { return a - b; }); } return timeoutKey; }; self.removeFunctionWithId = function(timeoutKey) { deletedKeys.push(timeoutKey); for (var runAtMillis in scheduledFunctions) { var funcs = scheduledFunctions[runAtMillis]; var i = indexOfFirstToPass(funcs, function(func) { return func.timeoutKey === timeoutKey; }); if (i > -1) { if (funcs.length === 1) { delete scheduledFunctions[runAtMillis]; deleteFromLookup(runAtMillis); } else { funcs.splice(i, 1); } // intervals get rescheduled when executed, so there's never more // than a single scheduled function with a given timeoutKey break; } } }; return self; function indexOfFirstToPass(array, testFn) { var index = -1; for (var i = 0; i < array.length; ++i) { if (testFn(array[i])) { index = i; break; } } return index; } function deleteFromLookup(key) { var value = Number(key); var i = indexOfFirstToPass(scheduledLookup, function(millis) { return millis === value; }); if (i > -1) { scheduledLookup.splice(i, 1); } } function reschedule(scheduledFn) { self.scheduleFunction( scheduledFn.funcToCall, scheduledFn.millis, scheduledFn.params, true, scheduledFn.timeoutKey, scheduledFn.runAtMillis + scheduledFn.millis ); } function forEachFunction(funcsToRun, callback) { for (var i = 0; i < funcsToRun.length; ++i) { callback(funcsToRun[i]); } } function runScheduledFunctions(endTime, tickDate) { tickDate = tickDate || function() {}; if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { tickDate(endTime - currentTime); return; } do { deletedKeys = []; var newCurrentTime = scheduledLookup.shift(); tickDate(newCurrentTime - currentTime); currentTime = newCurrentTime; var funcsToRun = scheduledFunctions[currentTime]; delete scheduledFunctions[currentTime]; forEachFunction(funcsToRun, function(funcToRun) { if (funcToRun.recurring) { reschedule(funcToRun); } }); forEachFunction(funcsToRun, function(funcToRun) { if (j$.util.arrayContains(deletedKeys, funcToRun.timeoutKey)) { // skip a timeoutKey deleted whilst we were running return; } funcToRun.funcToCall.apply(null, funcToRun.params || []); }); deletedKeys = []; } while ( scheduledLookup.length > 0 && // checking first if we're out of time prevents setTimeout(0) // scheduled in a funcToRun from forcing an extra iteration currentTime !== endTime && scheduledLookup[0] <= endTime ); // ran out of functions to call, but still time left on the clock if (currentTime !== endTime) { tickDate(endTime - currentTime); } } } return DelayedFunctionScheduler; }; getJasmineRequireObj().errors = function() { function ExpectationFailed() {} ExpectationFailed.prototype = new Error(); ExpectationFailed.prototype.constructor = ExpectationFailed; return { ExpectationFailed: ExpectationFailed }; }; getJasmineRequireObj().ExceptionFormatter = function(j$) { var ignoredProperties = [ 'name', 'message', 'stack', 'fileName', 'sourceURL', 'line', 'lineNumber', 'column', 'description', 'jasmineMessage' ]; function ExceptionFormatter(options) { var jasmineFile = (options && options.jasmineFile) || j$.util.jasmineFile(); this.message = function(error) { var message = ''; if (error.jasmineMessage) { message += error.jasmineMessage; } else if (error.name && error.message) { message += error.name + ': ' + error.message; } else if (error.message) { message += error.message; } else { message += error.toString() + ' thrown'; } if (error.fileName || error.sourceURL) { message += ' in ' + (error.fileName || error.sourceURL); } if (error.line || error.lineNumber) { message += ' (line ' + (error.line || error.lineNumber) + ')'; } return message; }; this.stack = function(error) { if (!error || !error.stack) { return null; } var stackTrace = new j$.StackTrace(error); var lines = filterJasmine(stackTrace); var result = ''; if (stackTrace.message) { lines.unshift(stackTrace.message); } result += formatProperties(error); result += lines.join('\n'); return result; }; function filterJasmine(stackTrace) { var result = [], jasmineMarker = stackTrace.style === 'webkit' ? '' : ' at '; stackTrace.frames.forEach(function(frame) { if (frame.file && frame.file !== jasmineFile) { result.push(frame.raw); } else if (result.length && result[result.length - 1] !== jasmineMarker) { result.push(jasmineMarker); } }); return result; } function formatProperties(error) { if (!(error instanceof Object)) { return; } var result = {}; var empty = true; for (var prop in error) { if (j$.util.arrayContains(ignoredProperties, prop)) { continue; } result[prop] = error[prop]; empty = false; } if (!empty) { return 'error properties: ' + j$.pp(result) + '\n'; } return ''; } } return ExceptionFormatter; }; getJasmineRequireObj().Expectation = function(j$) { /** * Matchers that come with Jasmine out of the box. * @namespace matchers */ function Expectation(options) { this.expector = new j$.Expector(options); var customMatchers = options.customMatchers || {}; for (var matcherName in customMatchers) { this[matcherName] = wrapSyncCompare( matcherName, customMatchers[matcherName] ); } } /** * Add some context for an {@link expect} * @function * @name matchers#withContext * @since 3.3.0 * @param {String} message - Additional context to show when the matcher fails * @return {matchers} */ Expectation.prototype.withContext = function withContext(message) { return addFilter(this, new ContextAddingFilter(message)); }; /** * Invert the matcher following this {@link expect} * @member * @name matchers#not * @since 1.3.0 * @type {matchers} * @example * expect(something).not.toBe(true); */ Object.defineProperty(Expectation.prototype, 'not', { get: function() { return addFilter(this, syncNegatingFilter); } }); /** * Asynchronous matchers. * @namespace async-matchers */ function AsyncExpectation(options) { var global = options.global || j$.getGlobal(); this.expector = new j$.Expector(options); if (!global.Promise) { throw new Error( 'expectAsync is unavailable because the environment does not support promises.' ); } var customAsyncMatchers = options.customAsyncMatchers || {}; for (var matcherName in customAsyncMatchers) { this[matcherName] = wrapAsyncCompare( matcherName, customAsyncMatchers[matcherName] ); } } /** * Add some context for an {@link expectAsync} * @function * @name async-matchers#withContext * @since 3.3.0 * @param {String} message - Additional context to show when the async matcher fails * @return {async-matchers} */ AsyncExpectation.prototype.withContext = function withContext(message) { return addFilter(this, new ContextAddingFilter(message)); }; /** * Invert the matcher following this {@link expectAsync} * @member * @name async-matchers#not * @type {async-matchers} * @example * await expectAsync(myPromise).not.toBeResolved(); * @example * return expectAsync(myPromise).not.toBeResolved(); */ Object.defineProperty(AsyncExpectation.prototype, 'not', { get: function() { return addFilter(this, asyncNegatingFilter); } }); function wrapSyncCompare(name, matcherFactory) { return function() { var result = this.expector.compare(name, matcherFactory, arguments); this.expector.processResult(result); }; } function wrapAsyncCompare(name, matcherFactory) { return function() { var self = this; // Capture the call stack here, before we go async, so that it will contain // frames that are relevant to the user instead of just parts of Jasmine. var errorForStack = j$.util.errorWithStack(); return this.expector .compare(name, matcherFactory, arguments) .then(function(result) { self.expector.processResult(result, errorForStack); }); }; } function addCoreMatchers(prototype, matchers, wrapper) { for (var matcherName in matchers) { var matcher = matchers[matcherName]; prototype[matcherName] = wrapper(matcherName, matcher); } } function addFilter(source, filter) { var result = Object.create(source); result.expector = source.expector.addFilter(filter); return result; } function negatedFailureMessage(result, matcherName, args, matchersUtil) { if (result.message) { if (j$.isFunction_(result.message)) { return result.message(); } else { return result.message; } } args = args.slice(); args.unshift(true); args.unshift(matcherName); return matchersUtil.buildFailureMessage.apply(matchersUtil, args); } function negate(result) { result.pass = !result.pass; return result; } var syncNegatingFilter = { selectComparisonFunc: function(matcher) { function defaultNegativeCompare() { return negate(matcher.compare.apply(null, arguments)); } return matcher.negativeCompare || defaultNegativeCompare; }, buildFailureMessage: negatedFailureMessage }; var asyncNegatingFilter = { selectComparisonFunc: function(matcher) { function defaultNegativeCompare() { return matcher.compare.apply(this, arguments).then(negate); } return matcher.negativeCompare || defaultNegativeCompare; }, buildFailureMessage: negatedFailureMessage }; function ContextAddingFilter(message) { this.message = message; } ContextAddingFilter.prototype.modifyFailureMessage = function(msg) { var nl = msg.indexOf('\n'); if (nl === -1) { return this.message + ': ' + msg; } else { return this.message + ':\n' + indent(msg); } }; function indent(s) { return s.replace(/^/gm, ' '); } return { factory: function(options) { return new Expectation(options || {}); }, addCoreMatchers: function(matchers) { addCoreMatchers(Expectation.prototype, matchers, wrapSyncCompare); }, asyncFactory: function(options) { return new AsyncExpectation(options || {}); }, addAsyncCoreMatchers: function(matchers) { addCoreMatchers(AsyncExpectation.prototype, matchers, wrapAsyncCompare); } }; }; getJasmineRequireObj().ExpectationFilterChain = function() { function ExpectationFilterChain(maybeFilter, prev) { this.filter_ = maybeFilter; this.prev_ = prev; } ExpectationFilterChain.prototype.addFilter = function(filter) { return new ExpectationFilterChain(filter, this); }; ExpectationFilterChain.prototype.selectComparisonFunc = function(matcher) { return this.callFirst_('selectComparisonFunc', arguments).result || undefined; }; ExpectationFilterChain.prototype.buildFailureMessage = function( result, matcherName, args, matchersUtil ) { return this.callFirst_('buildFailureMessage', arguments).result || undefined; }; ExpectationFilterChain.prototype.modifyFailureMessage = function(msg) { var result = this.callFirst_('modifyFailureMessage', arguments).result; return result || msg; }; ExpectationFilterChain.prototype.callFirst_ = function(fname, args) { var prevResult; if (this.prev_) { prevResult = this.prev_.callFirst_(fname, args); if (prevResult.found) { return prevResult; } } if (this.filter_ && this.filter_[fname]) { return { found: true, result: this.filter_[fname].apply(this.filter_, args) }; } return { found: false }; }; return ExpectationFilterChain; }; //TODO: expectation result may make more sense as a presentation of an expectation. getJasmineRequireObj().buildExpectationResult = function(j$) { function buildExpectationResult(options) { var messageFormatter = options.messageFormatter || function() {}, stackFormatter = options.stackFormatter || function() {}; /** * @typedef Expectation * @property {String} matcherName - The name of the matcher that was executed for this expectation. * @property {String} message - The failure message for the expectation. * @property {String} stack - The stack trace for the failure if available. * @property {Boolean} passed - Whether the expectation passed or failed. * @property {Object} expected - If the expectation failed, what was the expected value. * @property {Object} actual - If the expectation failed, what actual value was produced. */ var result = { matcherName: options.matcherName, message: message(), stack: stack(), passed: options.passed }; if (!result.passed) { result.expected = options.expected; result.actual = options.actual; if (options.error && !j$.isString_(options.error)) { if ('code' in options.error) { result.code = options.error.code; } if ( options.error.code === 'ERR_ASSERTION' && options.expected === '' && options.actual === '' ) { result.expected = options.error.expected; result.actual = options.error.actual; result.matcherName = 'assert ' + options.error.operator; } } } return result; function message() { if (options.passed) { return 'Passed.'; } else if (options.message) { return options.message; } else if (options.error) { return messageFormatter(options.error); } return ''; } function stack() { if (options.passed) { return ''; } var error = options.error; if (!error) { if (options.errorForStack) { error = options.errorForStack; } else if (options.stack) { error = options; } else { try { throw new Error(message()); } catch (e) { error = e; } } } return stackFormatter(error); } } return buildExpectationResult; }; getJasmineRequireObj().Expector = function(j$) { function Expector(options) { this.matchersUtil = options.matchersUtil || { buildFailureMessage: function() {} }; this.customEqualityTesters = options.customEqualityTesters || []; this.actual = options.actual; this.addExpectationResult = options.addExpectationResult || function() {}; this.filters = new j$.ExpectationFilterChain(); } Expector.prototype.instantiateMatcher = function( matcherName, matcherFactory, args ) { this.matcherName = matcherName; this.args = Array.prototype.slice.call(args, 0); this.expected = this.args.slice(0); this.args.unshift(this.actual); var matcher = matcherFactory(this.matchersUtil, this.customEqualityTesters); var comparisonFunc = this.filters.selectComparisonFunc(matcher); return comparisonFunc || matcher.compare; }; Expector.prototype.buildMessage = function(result) { var self = this; if (result.pass) { return ''; } var msg = this.filters.buildFailureMessage( result, this.matcherName, this.args, this.matchersUtil, defaultMessage ); return this.filters.modifyFailureMessage(msg || defaultMessage()); function defaultMessage() { if (!result.message) { var args = self.args.slice(); args.unshift(false); args.unshift(self.matcherName); return self.matchersUtil.buildFailureMessage.apply( self.matchersUtil, args ); } else if (j$.isFunction_(result.message)) { return result.message(); } else { return result.message; } } }; Expector.prototype.compare = function(matcherName, matcherFactory, args) { var matcherCompare = this.instantiateMatcher( matcherName, matcherFactory, args ); return matcherCompare.apply(null, this.args); }; Expector.prototype.addFilter = function(filter) { var result = Object.create(this); result.filters = this.filters.addFilter(filter); return result; }; Expector.prototype.processResult = function(result, errorForStack) { var message = this.buildMessage(result); if (this.expected.length === 1) { this.expected = this.expected[0]; } this.addExpectationResult(result.pass, { matcherName: this.matcherName, passed: result.pass, message: message, error: errorForStack ? undefined : result.error, errorForStack: errorForStack || undefined, actual: this.actual, expected: this.expected // TODO: this may need to be arrayified/sliced }); }; return Expector; }; getJasmineRequireObj().formatErrorMsg = function() { function generateErrorMsg(domain, usage) { var usageDefinition = usage ? '\nUsage: ' + usage : ''; return function errorMsg(msg) { return domain + ' : ' + msg + usageDefinition; }; } return generateErrorMsg; }; getJasmineRequireObj().GlobalErrors = function(j$) { function GlobalErrors(global) { var handlers = []; global = global || j$.getGlobal(); var onerror = function onerror() { var handler = handlers[handlers.length - 1]; if (handler) { handler.apply(null, Array.prototype.slice.call(arguments, 0)); } else { throw arguments[0]; } }; this.originalHandlers = {}; this.jasmineHandlers = {}; this.installOne_ = function installOne_(errorType, jasmineMessage) { function taggedOnError(error) { error.jasmineMessage = jasmineMessage + ': ' + error; var handler = handlers[handlers.length - 1]; if (handler) { handler(error); } else { throw error; } } this.originalHandlers[errorType] = global.process.listeners(errorType); this.jasmineHandlers[errorType] = taggedOnError; global.process.removeAllListeners(errorType); global.process.on(errorType, taggedOnError); this.uninstall = function uninstall() { var errorTypes = Object.keys(this.originalHandlers); for (var iType = 0; iType < errorTypes.length; iType++) { var errorType = errorTypes[iType]; global.process.removeListener( errorType, this.jasmineHandlers[errorType] ); for (var i = 0; i < this.originalHandlers[errorType].length; i++) { global.process.on(errorType, this.originalHandlers[errorType][i]); } delete this.originalHandlers[errorType]; delete this.jasmineHandlers[errorType]; } }; }; this.install = function install() { if ( global.process && global.process.listeners && j$.isFunction_(global.process.on) ) { this.installOne_('uncaughtException', 'Uncaught exception'); this.installOne_('unhandledRejection', 'Unhandled promise rejection'); } else { var originalHandler = global.onerror; global.onerror = onerror; var browserRejectionHandler = function browserRejectionHandler(event) { if (j$.isError_(event.reason)) { event.reason.jasmineMessage = 'Unhandled promise rejection: ' + event.reason; onerror(event.reason); } else { onerror('Unhandled promise rejection: ' + event.reason); } }; if (global.addEventListener) { global.addEventListener( 'unhandledrejection', browserRejectionHandler ); } this.uninstall = function uninstall() { global.onerror = originalHandler; if (global.removeEventListener) { global.removeEventListener( 'unhandledrejection', browserRejectionHandler ); } }; } }; this.pushListener = function pushListener(listener) { handlers.push(listener); }; this.popListener = function popListener() { handlers.pop(); }; } return GlobalErrors; }; /* eslint-disable compat/compat */ getJasmineRequireObj().toBePending = function(j$) { /** * Expect a promise to be pending, ie. the promise is neither resolved nor rejected. * @function * @async * @name async-matchers#toBePending * @since 3.6 * @example * await expectAsync(aPromise).toBePending(); */ return function toBePending() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { throw new Error('Expected toBePending to be called on a promise.'); } var want = {}; return Promise.race([actual, Promise.resolve(want)]).then( function(got) { return {pass: want === got}; }, function() { return {pass: false}; } ); } }; }; }; getJasmineRequireObj().toBeRejected = function(j$) { /** * Expect a promise to be rejected. * @function * @async * @name async-matchers#toBeRejected * @since 3.1.0 * @example * await expectAsync(aPromise).toBeRejected(); * @example * return expectAsync(aPromise).toBeRejected(); */ return function toBeRejected() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { throw new Error('Expected toBeRejected to be called on a promise.'); } return actual.then( function() { return {pass: false}; }, function() { return {pass: true}; } ); } }; }; }; getJasmineRequireObj().toBeRejectedWith = function(j$) { /** * Expect a promise to be rejected with a value equal to the expected, using deep equality comparison. * @function * @async * @name async-matchers#toBeRejectedWith * @since 3.3.0 * @param {Object} expected - Value that the promise is expected to be rejected with * @example * await expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ return function toBeRejectedWith(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { throw new Error('Expected toBeRejectedWith to be called on a promise.'); } function prefix(passed) { return 'Expected a promise ' + (passed ? 'not ' : '') + 'to be rejected with ' + matchersUtil.pp(expectedValue); } return actualPromise.then( function() { return { pass: false, message: prefix(false) + ' but it was resolved.' }; }, function(actualValue) { if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' }; } else { return { pass: false, message: prefix(false) + ' but it was rejected with ' + matchersUtil.pp(actualValue) + '.' }; } } ); } }; }; }; getJasmineRequireObj().toBeRejectedWithError = function(j$) { /** * Expect a promise to be rejected with a value matched to the expected * @function * @async * @name async-matchers#toBeRejectedWithError * @since 3.5.0 * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used. * @param {RegExp|String} [message] - The message that should be set on the thrown `Error` * @example * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, 'Error message'); * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, /Error message/); * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError); * await expectAsync(aPromise).toBeRejectedWithError('Error message'); * return expectAsync(aPromise).toBeRejectedWithError(/Error message/); */ return function toBeRejectedWithError(matchersUtil) { return { compare: function(actualPromise, arg1, arg2) { if (!j$.isPromiseLike(actualPromise)) { throw new Error('Expected toBeRejectedWithError to be called on a promise.'); } var expected = getExpectedFromArgs(arg1, arg2, matchersUtil); return actualPromise.then( function() { return { pass: false, message: 'Expected a promise to be rejected but it was resolved.' }; }, function(actualValue) { return matchError(actualValue, expected, matchersUtil); } ); } }; }; function matchError(actual, expected, matchersUtil) { if (!j$.isError_(actual)) { return fail(expected, 'rejected with ' + matchersUtil.pp(actual)); } if (!(actual instanceof expected.error)) { return fail(expected, 'rejected with type ' + j$.fnNameFor(actual.constructor)); } var actualMessage = actual.message; if (actualMessage === expected.message || typeof expected.message === 'undefined') { return pass(expected); } if (expected.message instanceof RegExp && expected.message.test(actualMessage)) { return pass(expected); } return fail(expected, 'rejected with ' + matchersUtil.pp(actual)); } function pass(expected) { return { pass: true, message: 'Expected a promise not to be rejected with ' + expected.printValue + ', but it was.' }; } function fail(expected, message) { return { pass: false, message: 'Expected a promise to be rejected with ' + expected.printValue + ' but it was ' + message + '.' }; } function getExpectedFromArgs(arg1, arg2, matchersUtil) { var error, message; if (isErrorConstructor(arg1)) { error = arg1; message = arg2; } else { error = Error; message = arg1; } return { error: error, message: message, printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' + matchersUtil.pp(message)) }; } function isErrorConstructor(value) { return typeof value === 'function' && (value === Error || j$.isError_(value.prototype)); } }; getJasmineRequireObj().toBeResolved = function(j$) { /** * Expect a promise to be resolved. * @function * @async * @name async-matchers#toBeResolved * @since 3.1.0 * @example * await expectAsync(aPromise).toBeResolved(); * @example * return expectAsync(aPromise).toBeResolved(); */ return function toBeResolved() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { throw new Error('Expected toBeResolved to be called on a promise.'); } return actual.then( function() { return {pass: true}; }, function() { return {pass: false}; } ); } }; }; }; getJasmineRequireObj().toBeResolvedTo = function(j$) { /** * Expect a promise to be resolved to a value equal to the expected, using deep equality comparison. * @function * @async * @name async-matchers#toBeResolvedTo * @since 3.1.0 * @param {Object} expected - Value that the promise is expected to resolve to * @example * await expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ return function toBeResolvedTo(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { throw new Error('Expected toBeResolvedTo to be called on a promise.'); } function prefix(passed) { return 'Expected a promise ' + (passed ? 'not ' : '') + 'to be resolved to ' + matchersUtil.pp(expectedValue); } return actualPromise.then( function(actualValue) { if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' }; } else { return { pass: false, message: prefix(false) + ' but it was resolved to ' + matchersUtil.pp(actualValue) + '.' }; } }, function() { return { pass: false, message: prefix(false) + ' but it was rejected.' }; } ); } }; }; }; getJasmineRequireObj().DiffBuilder = function (j$) { return function DiffBuilder(config) { var prettyPrinter = (config || {}).prettyPrinter || j$.makePrettyPrinter(), mismatches = new j$.MismatchTree(), path = new j$.ObjectPath(), actualRoot = undefined, expectedRoot = undefined; return { setRoots: function (actual, expected) { actualRoot = actual; expectedRoot = expected; }, recordMismatch: function (formatter) { mismatches.add(path, formatter); }, getMessage: function () { var messages = []; mismatches.traverse(function (path, isLeaf, formatter) { var actualCustom, expectedCustom, useCustom, derefResult = dereferencePath(path, actualRoot, expectedRoot, prettyPrinter), actual = derefResult.actual, expected = derefResult.expected; if (formatter) { messages.push(formatter(actual, expected, path, prettyPrinter)); return true; } actualCustom = prettyPrinter.customFormat_(actual); expectedCustom = prettyPrinter.customFormat_(expected); useCustom = !(j$.util.isUndefined(actualCustom) && j$.util.isUndefined(expectedCustom)); if (useCustom) { messages.push(wrapPrettyPrinted(actualCustom, expectedCustom, path)); return false; // don't recurse further } if (isLeaf) { messages.push(defaultFormatter(actual, expected, path, prettyPrinter)); } return true; }); return messages.join('\n'); }, withPath: function (pathComponent, block) { var oldPath = path; path = path.add(pathComponent); block(); path = oldPath; } }; function defaultFormatter(actual, expected, path, prettyPrinter) { return wrapPrettyPrinted(prettyPrinter(actual), prettyPrinter(expected), path); } function wrapPrettyPrinted(actual, expected, path) { return 'Expected ' + path + (path.depth() ? ' = ' : '') + actual + ' to equal ' + expected + '.'; } }; function dereferencePath(objectPath, actual, expected, pp) { function handleAsymmetricExpected() { if (j$.isAsymmetricEqualityTester_(expected) && j$.isFunction_(expected.valuesForDiff_)) { var asymmetricResult = expected.valuesForDiff_(actual, pp); expected = asymmetricResult.self; actual = asymmetricResult.other; } } var i; handleAsymmetricExpected(); for (i = 0; i < objectPath.components.length; i++) { actual = actual[objectPath.components[i]]; expected = expected[objectPath.components[i]]; handleAsymmetricExpected(); } return {actual: actual, expected: expected}; } }; getJasmineRequireObj().MatchersUtil = function(j$) { // TODO: convert all uses of j$.pp to use the injected pp /** * _Note:_ Do not construct this directly. Jasmine will construct one and * pass it to matchers and asymmetric equality testers. * @name MatchersUtil * @classdesc Utilities for use in implementing matchers * @constructor */ function MatchersUtil(options) { options = options || {}; this.customTesters_ = options.customTesters || []; /** * Formats a value for use in matcher failure messages and similar contexts, * taking into account the current set of custom value formatters. * @function * @name MatchersUtil#pp * @since 3.6.0 * @param {*} value The value to pretty-print * @return {string} The pretty-printed value */ this.pp = options.pp || function() {}; }; /** * Determines whether `haystack` contains `needle`, using the same comparison * logic as {@link MatchersUtil#equals}. * @function * @name MatchersUtil#contains * @since 2.0.0 * @param {*} haystack The collection to search * @param {*} needle The value to search for * @param [customTesters] An array of custom equality testers * @returns {boolean} True if `needle` was found in `haystack` */ MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { if (j$.isSet(haystack)) { return haystack.has(needle); } if ((Object.prototype.toString.apply(haystack) === '[object Array]') || (!!haystack && !haystack.indexOf)) { for (var i = 0; i < haystack.length; i++) { if (this.equals(haystack[i], needle, customTesters)) { return true; } } return false; } return !!haystack && haystack.indexOf(needle) >= 0; }; MatchersUtil.prototype.buildFailureMessage = function() { var self = this; var args = Array.prototype.slice.call(arguments, 0), matcherName = args[0], isNot = args[1], actual = args[2], expected = args.slice(3), englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); var message = 'Expected ' + self.pp(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; if (expected.length > 0) { for (var i = 0; i < expected.length; i++) { if (i > 0) { message += ','; } message += ' ' + self.pp(expected[i]); } } return message + '.'; }; MatchersUtil.prototype.asymmetricDiff_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { if (j$.isFunction_(b.valuesForDiff_)) { var values = b.valuesForDiff_(a, this.pp); this.eq_(values.other, values.self, aStack, bStack, customTesters, diffBuilder); } else { diffBuilder.recordMismatch(); } }; MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = j$.isAsymmetricEqualityTester_(a), asymmetricB = j$.isAsymmetricEqualityTester_(b), shim, result; if (asymmetricA === asymmetricB) { return undefined; } shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters); if (asymmetricA) { result = a.asymmetricMatch(b, shim); if (!result) { diffBuilder.recordMismatch(); } return result; } if (asymmetricB) { result = b.asymmetricMatch(a, shim); if (!result) { this.asymmetricDiff_(a, b, aStack, bStack, customTesters, diffBuilder); } return result; } }; /** * Determines whether two values are deeply equal to each other. * @function * @name MatchersUtil#equals * @since 2.0.0 * @param {*} a The first value to compare * @param {*} b The second value to compare * @param [customTesters] An array of custom equality testers * @returns {boolean} True if the values are equal */ MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) { var customTesters, diffBuilder; if (isDiffBuilder(customTestersOrDiffBuilder)) { diffBuilder = customTestersOrDiffBuilder; } else { customTesters = customTestersOrDiffBuilder; diffBuilder = diffBuilderOrNothing; } customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); diffBuilder.setRoots(a, b); return this.eq_(a, b, [], [], customTesters, diffBuilder); }; // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) MatchersUtil.prototype.eq_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var result = true, self = this, i; var asymmetricResult = this.asymmetricMatch_(a, b, aStack, bStack, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } for (i = 0; i < customTesters.length; i++) { var customTesterResult = customTesters[i](a, b); if (!j$.util.isUndefined(customTesterResult)) { if (!customTesterResult) { diffBuilder.recordMismatch(); } return customTesterResult; } } if (a instanceof Error && b instanceof Error) { result = a.message == b.message; if (!result) { diffBuilder.recordMismatch(); } return result; } // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) { result = a !== 0 || 1 / a == 1 / b; if (!result) { diffBuilder.recordMismatch(); } return result; } // A strict comparison is necessary because `null == undefined`. if (a === null || b === null) { result = a === b; if (!result) { diffBuilder.recordMismatch(); } return result; } var className = Object.prototype.toString.call(a); if (className != Object.prototype.toString.call(b)) { diffBuilder.recordMismatch(); return false; } switch (className) { // Strings, numbers, dates, and booleans are compared by value. case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. result = a == String(b); if (!result) { diffBuilder.recordMismatch(); } return result; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // other numeric values. result = a != +a ? b != +b : (a === 0 && b === 0 ? 1 / a == 1 / b : a == +b); if (!result) { diffBuilder.recordMismatch(); } return result; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. result = +a == +b; if (!result) { diffBuilder.recordMismatch(); } return result; // RegExps are compared by their source patterns and flags. case '[object RegExp]': return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; } if (typeof a != 'object' || typeof b != 'object') { diffBuilder.recordMismatch(); return false; } var aIsDomNode = j$.isDomNode(a); var bIsDomNode = j$.isDomNode(b); if (aIsDomNode && bIsDomNode) { // At first try to use DOM3 method isEqualNode result = a.isEqualNode(b); if (!result) { diffBuilder.recordMismatch(); } return result; } if (aIsDomNode || bIsDomNode) { diffBuilder.recordMismatch(); return false; } var aIsPromise = j$.isPromise(a); var bIsPromise = j$.isPromise(b); if (aIsPromise && bIsPromise) { return a === b; } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] == a) { return bStack[length] == b; } } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); var size = 0; // Recursively compare objects and arrays. // Compare array lengths to determine if a deep comparison is necessary. if (className == '[object Array]') { var aLength = a.length; var bLength = b.length; diffBuilder.withPath('length', function() { if (aLength !== bLength) { diffBuilder.recordMismatch(); result = false; } }); for (i = 0; i < aLength || i < bLength; i++) { diffBuilder.withPath(i, function() { if (i >= bLength) { diffBuilder.recordMismatch(actualArrayIsLongerFormatter.bind(null, self.pp)); result = false; } else { result = self.eq_(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; } }); } if (!result) { return false; } } else if (j$.isMap(a) && j$.isMap(b)) { if (a.size != b.size) { diffBuilder.recordMismatch(); return false; } var keysA = []; var keysB = []; a.forEach( function( valueA, keyA ) { keysA.push( keyA ); }); b.forEach( function( valueB, keyB ) { keysB.push( keyB ); }); // For both sets of keys, check they map to equal values in both maps. // Keep track of corresponding keys (in insertion order) in order to handle asymmetric obj keys. var mapKeys = [keysA, keysB]; var cmpKeys = [keysB, keysA]; var mapIter, mapKey, mapValueA, mapValueB; var cmpIter, cmpKey; for (i = 0; result && i < mapKeys.length; i++) { mapIter = mapKeys[i]; cmpIter = cmpKeys[i]; for (var j = 0; result && j < mapIter.length; j++) { mapKey = mapIter[j]; cmpKey = cmpIter[j]; mapValueA = a.get(mapKey); // Only use the cmpKey when one of the keys is asymmetric and the corresponding key matches, // otherwise explicitly look up the mapKey in the other Map since we want keys with unique // obj identity (that are otherwise equal) to not match. if (j$.isAsymmetricEqualityTester_(mapKey) || j$.isAsymmetricEqualityTester_(cmpKey) && this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { mapValueB = b.get(cmpKey); } else { mapValueB = b.get(mapKey); } result = this.eq_(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); } } if (!result) { diffBuilder.recordMismatch(); return false; } } else if (j$.isSet(a) && j$.isSet(b)) { if (a.size != b.size) { diffBuilder.recordMismatch(); return false; } var valuesA = []; a.forEach( function( valueA ) { valuesA.push( valueA ); }); var valuesB = []; b.forEach( function( valueB ) { valuesB.push( valueB ); }); // For both sets, check they are all contained in the other set var setPairs = [[valuesA, valuesB], [valuesB, valuesA]]; var stackPairs = [[aStack, bStack], [bStack, aStack]]; var baseValues, baseValue, baseStack; var otherValues, otherValue, otherStack; var found; var prevStackSize; for (i = 0; result && i < setPairs.length; i++) { baseValues = setPairs[i][0]; otherValues = setPairs[i][1]; baseStack = stackPairs[i][0]; otherStack = stackPairs[i][1]; // For each value in the base set... for (var k = 0; result && k < baseValues.length; k++) { baseValue = baseValues[k]; found = false; // ... test that it is present in the other set for (var l = 0; !found && l < otherValues.length; l++) { otherValue = otherValues[l]; prevStackSize = baseStack.length; // compare by value equality found = this.eq_(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); if (!found && prevStackSize !== baseStack.length) { baseStack.splice(prevStackSize); otherStack.splice(prevStackSize); } } result = result && found; } } if (!result) { diffBuilder.recordMismatch(); return false; } } else { // Objects with different constructors are not equivalent, but `Object`s // or `Array`s from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && isFunction(aCtor) && isFunction(bCtor) && a instanceof aCtor && b instanceof bCtor && !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { diffBuilder.recordMismatch(constructorsAreDifferentFormatter.bind(null, this.pp)); return false; } } // Deep compare objects. var aKeys = keys(a, className == '[object Array]'), key; size = aKeys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. if (keys(b, className == '[object Array]').length !== size) { diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp)); return false; } for (i = 0; i < size; i++) { key = aKeys[i]; // Deep compare each member if (!j$.util.has(b, key)) { diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp)); result = false; continue; } diffBuilder.withPath(key, function() { if(!self.eq_(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { result = false; } }); } if (!result) { return false; } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return result; }; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { var keys = []; for (var key in o) { if (j$.util.has(o, key)) { keys.push(key); } } return keys; })(obj); if (!isArray) { return allKeys; } if (allKeys.length === 0) { return allKeys; } var extraKeys = []; for (var i = 0; i < allKeys.length; i++) { if (!/^[0-9]+$/.test(allKeys[i])) { extraKeys.push(allKeys[i]); } } return extraKeys; } function isFunction(obj) { return typeof obj === 'function'; } function objectKeysAreDifferentFormatter(pp, actual, expected, path) { var missingProperties = j$.util.objectDifference(expected, actual), extraProperties = j$.util.objectDifference(actual, expected), missingPropertiesMessage = formatKeyValuePairs(pp, missingProperties), extraPropertiesMessage = formatKeyValuePairs(pp, extraProperties), messages = []; if (!path.depth()) { path = 'object'; } if (missingPropertiesMessage.length) { messages.push('Expected ' + path + ' to have properties' + missingPropertiesMessage); } if (extraPropertiesMessage.length) { messages.push('Expected ' + path + ' not to have properties' + extraPropertiesMessage); } return messages.join('\n'); } function constructorsAreDifferentFormatter(pp, actual, expected, path) { if (!path.depth()) { path = 'object'; } return 'Expected ' + path + ' to be a kind of ' + j$.fnNameFor(expected.constructor) + ', but was ' + pp(actual) + '.'; } function actualArrayIsLongerFormatter(pp, actual, expected, path) { return 'Unexpected ' + path + (path.depth() ? ' = ' : '') + pp(actual) + ' in array.'; } function formatKeyValuePairs(pp, obj) { var formatted = ''; for (var key in obj) { formatted += '\n ' + key + ': ' + pp(obj[key]); } return formatted; } function isDiffBuilder(obj) { return obj && typeof obj.recordMismatch === 'function'; } return MatchersUtil; }; getJasmineRequireObj().MismatchTree = function (j$) { /* To be able to apply custom object formatters at all possible levels of an object graph, DiffBuilder needs to be able to know not just where the mismatch occurred but also all ancestors of the mismatched value in both the expected and actual object graphs. MismatchTree maintains that context and provides it via the traverse method. */ function MismatchTree(path) { this.path = path || new j$.ObjectPath([]); this.formatter = undefined; this.children = []; this.isMismatch = false; } MismatchTree.prototype.add = function (path, formatter) { var key, child; if (path.depth() === 0) { this.formatter = formatter; this.isMismatch = true; } else { key = path.components[0]; path = path.shift(); child = this.child(key); if (!child) { child = new MismatchTree(this.path.add(key)); this.children.push(child); } child.add(path, formatter); } }; MismatchTree.prototype.traverse = function (visit) { var i, hasChildren = this.children.length > 0; if (this.isMismatch || hasChildren) { if (visit(this.path, !hasChildren, this.formatter)) { for (i = 0; i < this.children.length; i++) { this.children[i].traverse(visit); } } } }; MismatchTree.prototype.child = function(key) { var i, pathEls; for (i = 0; i < this.children.length; i++) { pathEls = this.children[i].path.components; if (pathEls[pathEls.length - 1] === key) { return this.children[i]; } } }; return MismatchTree; }; getJasmineRequireObj().nothing = function() { /** * {@link expect} nothing explicitly. * @function * @name matchers#nothing * @since 2.8.0 * @example * expect().nothing(); */ function nothing() { return { compare: function() { return { pass: true }; } }; } return nothing; }; getJasmineRequireObj().NullDiffBuilder = function(j$) { return function() { return { withPath: function(_, block) { block(); }, setRoots: function() {}, recordMismatch: function() {} }; }; }; getJasmineRequireObj().ObjectPath = function(j$) { function ObjectPath(components) { this.components = components || []; } ObjectPath.prototype.toString = function() { if (this.components.length) { return '$' + map(this.components, formatPropertyAccess).join(''); } else { return ''; } }; ObjectPath.prototype.add = function(component) { return new ObjectPath(this.components.concat([component])); }; ObjectPath.prototype.shift = function() { return new ObjectPath(this.components.slice(1)); }; ObjectPath.prototype.depth = function() { return this.components.length; }; function formatPropertyAccess(prop) { if (typeof prop === 'number') { return '[' + prop + ']'; } if (isValidIdentifier(prop)) { return '.' + prop; } return '[\'' + prop + '\']'; } function map(array, fn) { var results = []; for (var i = 0; i < array.length; i++) { results.push(fn(array[i])); } return results; } function isValidIdentifier(string) { return /^[A-Za-z\$_][A-Za-z0-9\$_]*$/.test(string); } return ObjectPath; }; getJasmineRequireObj().requireAsyncMatchers = function(jRequire, j$) { var availableMatchers = [ 'toBePending', 'toBeResolved', 'toBeRejected', 'toBeResolvedTo', 'toBeRejectedWith', 'toBeRejectedWithError' ], matchers = {}; for (var i = 0; i < availableMatchers.length; i++) { var name = availableMatchers[i]; matchers[name] = jRequire[name](j$); } return matchers; }; getJasmineRequireObj().toBe = function(j$) { /** * {@link expect} the actual value to be `===` to the expected value. * @function * @name matchers#toBe * @since 1.3.0 * @param {Object} expected - The expected value to compare against. * @example * expect(thing).toBe(realThing); */ function toBe(matchersUtil) { var tip = ' Tip: To check for deep equality, use .toEqual() instead of .toBe().'; return { compare: function(actual, expected) { var result = { pass: actual === expected }; if (typeof expected === 'object') { result.message = matchersUtil.buildFailureMessage('toBe', result.pass, actual, expected) + tip; } return result; } }; } return toBe; }; getJasmineRequireObj().toBeCloseTo = function() { /** * {@link expect} the actual value to be within a specified precision of the expected value. * @function * @name matchers#toBeCloseTo * @since 1.3.0 * @param {Object} expected - The expected value to compare against. * @param {Number} [precision=2] - The number of decimal points to check. * @example * expect(number).toBeCloseTo(42.2, 3); */ function toBeCloseTo() { return { compare: function(actual, expected, precision) { if (precision !== 0) { precision = precision || 2; } if (expected === null || actual === null) { throw new Error('Cannot use toBeCloseTo with null. Arguments evaluated to: ' + 'expect(' + actual + ').toBeCloseTo(' + expected + ').' ); } var pow = Math.pow(10, precision + 1); var delta = Math.abs(expected - actual); var maxDelta = Math.pow(10, -precision) / 2; return { pass: Math.round(delta * pow) <= maxDelta * pow }; } }; } return toBeCloseTo; }; getJasmineRequireObj().toBeDefined = function() { /** * {@link expect} the actual value to be defined. (Not `undefined`) * @function * @name matchers#toBeDefined * @since 1.3.0 * @example * expect(result).toBeDefined(); */ function toBeDefined() { return { compare: function(actual) { return { pass: (void 0 !== actual) }; } }; } return toBeDefined; }; getJasmineRequireObj().toBeFalse = function() { /** * {@link expect} the actual value to be `false`. * @function * @name matchers#toBeFalse * @since 3.5.0 * @example * expect(result).toBeFalse(); */ function toBeFalse() { return { compare: function(actual) { return { pass: actual === false }; } }; } return toBeFalse; }; getJasmineRequireObj().toBeFalsy = function() { /** * {@link expect} the actual value to be falsy * @function * @name matchers#toBeFalsy * @since 2.0.0 * @example * expect(result).toBeFalsy(); */ function toBeFalsy() { return { compare: function(actual) { return { pass: !actual }; } }; } return toBeFalsy; }; getJasmineRequireObj().toBeGreaterThan = function() { /** * {@link expect} the actual value to be greater than the expected value. * @function * @name matchers#toBeGreaterThan * @since 2.0.0 * @param {Number} expected - The value to compare against. * @example * expect(result).toBeGreaterThan(3); */ function toBeGreaterThan() { return { compare: function(actual, expected) { return { pass: actual > expected }; } }; } return toBeGreaterThan; }; getJasmineRequireObj().toBeGreaterThanOrEqual = function() { /** * {@link expect} the actual value to be greater than or equal to the expected value. * @function * @name matchers#toBeGreaterThanOrEqual * @since 2.0.0 * @param {Number} expected - The expected value to compare against. * @example * expect(result).toBeGreaterThanOrEqual(25); */ function toBeGreaterThanOrEqual() { return { compare: function(actual, expected) { return { pass: actual >= expected }; } }; } return toBeGreaterThanOrEqual; }; getJasmineRequireObj().toBeInstanceOf = function(j$) { var usageError = j$.formatErrorMsg('', 'expect(value).toBeInstanceOf()'); /** * {@link expect} the actual to be an instance of the expected class * @function * @name matchers#toBeInstanceOf * @since 3.5.0 * @param {Object} expected - The class or constructor function to check for * @example * expect('foo').toBeInstanceOf(String); * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ function toBeInstanceOf(matchersUtil) { return { compare: function(actual, expected) { var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : matchersUtil.pp(actual), expectedType = expected ? j$.fnNameFor(expected) : matchersUtil.pp(expected), expectedMatcher, pass; try { expectedMatcher = new j$.Any(expected); pass = expectedMatcher.asymmetricMatch(actual); } catch (error) { throw new Error(usageError('Expected value is not a constructor function')); } if (pass) { return { pass: true, message: 'Expected instance of ' + actualType + ' not to be an instance of ' + expectedType }; } else { return { pass: false, message: 'Expected instance of ' + actualType + ' to be an instance of ' + expectedType }; } } }; } return toBeInstanceOf; }; getJasmineRequireObj().toBeLessThan = function() { /** * {@link expect} the actual value to be less than the expected value. * @function * @name matchers#toBeLessThan * @since 2.0.0 * @param {Number} expected - The expected value to compare against. * @example * expect(result).toBeLessThan(0); */ function toBeLessThan() { return { compare: function(actual, expected) { return { pass: actual < expected }; } }; } return toBeLessThan; }; getJasmineRequireObj().toBeLessThanOrEqual = function() { /** * {@link expect} the actual value to be less than or equal to the expected value. * @function * @name matchers#toBeLessThanOrEqual * @since 2.0.0 * @param {Number} expected - The expected value to compare against. * @example * expect(result).toBeLessThanOrEqual(123); */ function toBeLessThanOrEqual() { return { compare: function(actual, expected) { return { pass: actual <= expected }; } }; } return toBeLessThanOrEqual; }; getJasmineRequireObj().toBeNaN = function(j$) { /** * {@link expect} the actual value to be `NaN` (Not a Number). * @function * @name matchers#toBeNaN * @since 1.3.0 * @example * expect(thing).toBeNaN(); */ function toBeNaN(matchersUtil) { return { compare: function(actual) { var result = { pass: (actual !== actual) }; if (result.pass) { result.message = 'Expected actual not to be NaN.'; } else { result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be NaN.'; }; } return result; } }; } return toBeNaN; }; getJasmineRequireObj().toBeNegativeInfinity = function(j$) { /** * {@link expect} the actual value to be `-Infinity` (-infinity). * @function * @name matchers#toBeNegativeInfinity * @since 2.6.0 * @example * expect(thing).toBeNegativeInfinity(); */ function toBeNegativeInfinity(matchersUtil) { return { compare: function(actual) { var result = { pass: (actual === Number.NEGATIVE_INFINITY) }; if (result.pass) { result.message = 'Expected actual not to be -Infinity.'; } else { result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be -Infinity.'; }; } return result; } }; } return toBeNegativeInfinity; }; getJasmineRequireObj().toBeNull = function() { /** * {@link expect} the actual value to be `null`. * @function * @name matchers#toBeNull * @since 1.3.0 * @example * expect(result).toBeNull(); */ function toBeNull() { return { compare: function(actual) { return { pass: actual === null }; } }; } return toBeNull; }; getJasmineRequireObj().toBePositiveInfinity = function(j$) { /** * {@link expect} the actual value to be `Infinity` (infinity). * @function * @name matchers#toBePositiveInfinity * @since 2.6.0 * @example * expect(thing).toBePositiveInfinity(); */ function toBePositiveInfinity(matchersUtil) { return { compare: function(actual) { var result = { pass: (actual === Number.POSITIVE_INFINITY) }; if (result.pass) { result.message = 'Expected actual not to be Infinity.'; } else { result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be Infinity.'; }; } return result; } }; } return toBePositiveInfinity; }; getJasmineRequireObj().toBeTrue = function() { /** * {@link expect} the actual value to be `true`. * @function * @name matchers#toBeTrue * @since 3.5.0 * @example * expect(result).toBeTrue(); */ function toBeTrue() { return { compare: function(actual) { return { pass: actual === true }; } }; } return toBeTrue; }; getJasmineRequireObj().toBeTruthy = function() { /** * {@link expect} the actual value to be truthy. * @function * @name matchers#toBeTruthy * @since 2.0.0 * @example * expect(thing).toBeTruthy(); */ function toBeTruthy() { return { compare: function(actual) { return { pass: !!actual }; } }; } return toBeTruthy; }; getJasmineRequireObj().toBeUndefined = function() { /** * {@link expect} the actual value to be `undefined`. * @function * @name matchers#toBeUndefined * @since 1.3.0 * @example * expect(result).toBeUndefined(): */ function toBeUndefined() { return { compare: function(actual) { return { pass: void 0 === actual }; } }; } return toBeUndefined; }; getJasmineRequireObj().toContain = function() { /** * {@link expect} the actual value to contain a specific value. * @function * @name matchers#toContain * @since 2.0.0 * @param {Object} expected - The value to look for. * @example * expect(array).toContain(anElement); * expect(string).toContain(substring); */ function toContain(matchersUtil) { return { compare: function(actual, expected) { return { pass: matchersUtil.contains(actual, expected) }; } }; } return toContain; }; getJasmineRequireObj().toEqual = function(j$) { /** * {@link expect} the actual value to be equal to the expected, using deep equality comparison. * @function * @name matchers#toEqual * @since 1.3.0 * @param {Object} expected - Expected value * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ function toEqual(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, diffBuilder = j$.DiffBuilder({prettyPrinter: matchersUtil.pp}); result.pass = matchersUtil.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); return result; } }; } return toEqual; }; getJasmineRequireObj().toHaveBeenCalled = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalled()'); /** * {@link expect} the actual (a {@link Spy}) to have been called. * @function * @name matchers#toHaveBeenCalled * @since 1.3.0 * @example * expect(mySpy).toHaveBeenCalled(); * expect(mySpy).not.toHaveBeenCalled(); */ function toHaveBeenCalled(matchersUtil) { return { compare: function(actual) { var result = {}; if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } if (arguments.length > 1) { throw new Error(getErrorMsg('Does not take arguments, use toHaveBeenCalledWith')); } result.pass = actual.calls.any(); result.message = result.pass ? 'Expected spy ' + actual.and.identity + ' not to have been called.' : 'Expected spy ' + actual.and.identity + ' to have been called.'; return result; } }; } return toHaveBeenCalled; }; getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledBefore()'); /** * {@link expect} the actual value (a {@link Spy}) to have been called before another {@link Spy}. * @function * @name matchers#toHaveBeenCalledBefore * @since 2.6.0 * @param {Spy} expected - {@link Spy} that should have been called after the `actual` {@link Spy}. * @example * expect(mySpy).toHaveBeenCalledBefore(otherSpy); */ function toHaveBeenCalledBefore(matchersUtil) { return { compare: function(firstSpy, latterSpy) { if (!j$.isSpy(firstSpy)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(firstSpy) + '.')); } if (!j$.isSpy(latterSpy)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(latterSpy) + '.')); } var result = { pass: false }; if (!firstSpy.calls.count()) { result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called.'; return result; } if (!latterSpy.calls.count()) { result.message = 'Expected spy ' + latterSpy.and.identity + ' to have been called.'; return result; } var latest1stSpyCall = firstSpy.calls.mostRecent().invocationOrder; var first2ndSpyCall = latterSpy.calls.first().invocationOrder; result.pass = latest1stSpyCall < first2ndSpyCall; if (result.pass) { result.message = 'Expected spy ' + firstSpy.and.identity + ' to not have been called before spy ' + latterSpy.and.identity + ', but it was'; } else { var first1stSpyCall = firstSpy.calls.first().invocationOrder; var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder; if(first1stSpyCall < first2ndSpyCall) { result.message = 'Expected latest call to spy ' + firstSpy.and.identity + ' to have been called before first call to spy ' + latterSpy.and.identity + ' (no interleaved calls)'; } else if (latest2ndSpyCall > latest1stSpyCall) { result.message = 'Expected first call to spy ' + latterSpy.and.identity + ' to have been called after latest call to spy ' + firstSpy.and.identity + ' (no interleaved calls)'; } else { result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called before spy ' + latterSpy.and.identity; } } return result; } }; } return toHaveBeenCalledBefore; }; getJasmineRequireObj().toHaveBeenCalledOnceWith = function (j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledOnceWith(...arguments)'); /** * {@link expect} the actual (a {@link Spy}) to have been called exactly once, and exactly with the particular arguments. * @function * @name matchers#toHaveBeenCalledOnceWith * @since 3.6.0 * @param {...Object} - The arguments to look for * @example * expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2); */ function toHaveBeenCalledOnceWith(util) { return { compare: function () { var args = Array.prototype.slice.call(arguments, 0), actual = args[0], expectedArgs = args.slice(1); if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); } var prettyPrintedCalls = actual.calls.allArgs().map(function (argsForCall) { return ' ' + j$.pp(argsForCall); }); if (actual.calls.count() === 1 && util.contains(actual.calls.allArgs(), expectedArgs)) { return { pass: true, message: 'Expected spy ' + actual.and.identity + ' to have been called 0 times, multiple times, or once, but with arguments different from:\n' + ' ' + j$.pp(expectedArgs) + '\n' + 'But the actual call was:\n' + prettyPrintedCalls.join(',\n') + '.\n\n' }; } function getDiffs() { return actual.calls.allArgs().map(function (argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); util.equals(argsForCall, expectedArgs, diffBuilder); return diffBuilder.getMessage(); }); } function butString() { switch (actual.calls.count()) { case 0: return 'But it was never called.\n\n'; case 1: return 'But the actual call was:\n' + prettyPrintedCalls.join(',\n') + '.\n' + getDiffs().join('\n') + '\n\n'; default: return 'But the actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n'; } } return { pass: false, message: 'Expected spy ' + actual.and.identity + ' to have been called only once, and with given args:\n' + ' ' + j$.pp(expectedArgs) + '\n' + butString() }; } }; } return toHaveBeenCalledOnceWith; }; getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledTimes()'); /** * {@link expect} the actual (a {@link Spy}) to have been called the specified number of times. * @function * @name matchers#toHaveBeenCalledTimes * @since 2.4.0 * @param {Number} expected - The number of invocations to look for. * @example * expect(mySpy).toHaveBeenCalledTimes(3); */ function toHaveBeenCalledTimes(matchersUtil) { return { compare: function(actual, expected) { if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } var args = Array.prototype.slice.call(arguments, 0), result = { pass: false }; if (!j$.isNumber_(expected)) { throw new Error(getErrorMsg('The expected times failed is a required argument and must be a number.')); } actual = args[0]; var calls = actual.calls.count(); var timesMessage = expected === 1 ? 'once' : expected + ' times'; result.pass = calls === expected; result.message = result.pass ? 'Expected spy ' + actual.and.identity + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : 'Expected spy ' + actual.and.identity + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; return result; } }; } return toHaveBeenCalledTimes; }; getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledWith(...arguments)'); /** * {@link expect} the actual (a {@link Spy}) to have been called with particular arguments at least once. * @function * @name matchers#toHaveBeenCalledWith * @since 1.3.0 * @param {...Object} - The arguments to look for * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ function toHaveBeenCalledWith(matchersUtil) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), actual = args[0], expectedArgs = args.slice(1), result = { pass: false }; if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } if (!actual.calls.any()) { result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' + ' ' + matchersUtil.pp(expectedArgs) + '\nbut it was never called.'; }; return result; } if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' + ' ' + matchersUtil.pp(expectedArgs) + '\nbut it was.'; }; } else { result.message = function() { var prettyPrintedCalls = actual.calls.allArgs().map(function(argsForCall) { return ' ' + matchersUtil.pp(argsForCall); }); var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); matchersUtil.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); }); return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' + ' ' + matchersUtil.pp(expectedArgs) + '\n' + '' + 'but actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n' + diffs.join('\n'); }; } return result; } }; } return toHaveBeenCalledWith; }; getJasmineRequireObj().toHaveClass = function(j$) { /** * {@link expect} the actual value to be a DOM element that has the expected class * @function * @name matchers#toHaveClass * @since 3.0.0 * @param {Object} expected - The class name to test for * @example * var el = document.createElement('div'); * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ function toHaveClass(matchersUtil) { return { compare: function(actual, expected) { if (!isElement(actual)) { throw new Error(matchersUtil.pp(actual) + ' is not a DOM element'); } return { pass: actual.classList.contains(expected) }; } }; } function isElement(maybeEl) { return maybeEl && maybeEl.classList && j$.isFunction_(maybeEl.classList.contains); } return toHaveClass; }; getJasmineRequireObj().toHaveSize = function(j$) { /** * {@link expect} the actual size to be equal to the expected, using array-like length or object keys size. * @function * @name matchers#toHaveSize * @since 3.6.0 * @param {Object} expected - Expected size * @example * array = [1,2]; * expect(array).toHaveSize(2); */ function toHaveSize() { return { compare: function(actual, expected) { var result = { pass: false }; if (j$.isA_('WeakSet', actual) || j$.isWeakMap(actual) || j$.isDataView(actual)) { throw new Error('Cannot get size of ' + actual + '.'); } if (j$.isSet(actual) || j$.isMap(actual)) { result.pass = actual.size === expected; } else if (isLength(actual.length)) { result.pass = actual.length === expected; } else { result.pass = Object.keys(actual).length === expected; } return result; } }; } var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // eslint-disable-line compat/compat function isLength(value) { return (typeof value == 'number') && value > -1 && value % 1 === 0 && value <= MAX_SAFE_INTEGER; } return toHaveSize; }; getJasmineRequireObj().toMatch = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toMatch( || )'); /** * {@link expect} the actual value to match a regular expression * @function * @name matchers#toMatch * @since 1.3.0 * @param {RegExp|String} expected - Value to look for in the string. * @example * expect("my string").toMatch(/string$/); * expect("other string").toMatch("her"); */ function toMatch() { return { compare: function(actual, expected) { if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { throw new Error(getErrorMsg('Expected is not a String or a RegExp')); } var regexp = new RegExp(expected); return { pass: regexp.test(actual) }; } }; } return toMatch; }; getJasmineRequireObj().toThrow = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect(function() {}).toThrow()'); /** * {@link expect} a function to `throw` something. * @function * @name matchers#toThrow * @since 2.0.0 * @param {Object} [expected] - Value that should be thrown. If not provided, simply the fact that something was thrown will be checked. * @example * expect(function() { return 'things'; }).toThrow('foo'); * expect(function() { return 'stuff'; }).toThrow(); */ function toThrow(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, threw = false, thrown; if (typeof actual != 'function') { throw new Error(getErrorMsg('Actual is not a Function')); } try { actual(); } catch (e) { threw = true; thrown = e; } if (!threw) { result.message = 'Expected function to throw an exception.'; return result; } if (arguments.length == 1) { result.pass = true; result.message = function() { return 'Expected function not to throw, but it threw ' + matchersUtil.pp(thrown) + '.'; }; return result; } if (matchersUtil.equals(thrown, expected)) { result.pass = true; result.message = function() { return 'Expected function not to throw ' + matchersUtil.pp(expected) + '.'; }; } else { result.message = function() { return 'Expected function to throw ' + matchersUtil.pp(expected) + ', but it threw ' + matchersUtil.pp(thrown) + '.'; }; } return result; } }; } return toThrow; }; getJasmineRequireObj().toThrowError = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect(function() {}).toThrowError(, )'); /** * {@link expect} a function to `throw` an `Error`. * @function * @name matchers#toThrowError * @since 2.0.0 * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used. * @param {RegExp|String} [message] - The message that should be set on the thrown `Error` * @example * expect(function() { return 'things'; }).toThrowError(MyCustomError, 'message'); * expect(function() { return 'things'; }).toThrowError(MyCustomError, /bar/); * expect(function() { return 'stuff'; }).toThrowError(MyCustomError); * expect(function() { return 'other'; }).toThrowError(/foo/); * expect(function() { return 'other'; }).toThrowError(); */ function toThrowError(matchersUtil) { return { compare: function(actual) { var errorMatcher = getMatcher.apply(null, arguments), thrown; if (typeof actual != 'function') { throw new Error(getErrorMsg('Actual is not a Function')); } try { actual(); return fail('Expected function to throw an Error.'); } catch (e) { thrown = e; } if (!j$.isError_(thrown)) { return fail(function() { return 'Expected function to throw an Error, but it threw ' + matchersUtil.pp(thrown) + '.'; }); } return errorMatcher.match(thrown); } }; function getMatcher() { var expected, errorType; if (arguments[2]) { errorType = arguments[1]; expected = arguments[2]; if (!isAnErrorType(errorType)) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } return exactMatcher(expected, errorType); } else if (arguments[1]) { expected = arguments[1]; if (isAnErrorType(arguments[1])) { return exactMatcher(null, arguments[1]); } else { return exactMatcher(arguments[1], null); } } else { return anyMatcher(); } } function anyMatcher() { return { match: function(error) { return pass('Expected function not to throw an Error, but it threw ' + j$.fnNameFor(error) + '.'); } }; } function exactMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); } } function messageMatch(message) { if (typeof expected == 'string') { return expected == message; } else { return expected.test(message); } } var errorTypeDescription = errorType ? j$.fnNameFor(errorType) : 'an exception'; function thrownDescription(thrown) { var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', thrownMessage = ''; if (expected) { thrownMessage = ' with message ' + matchersUtil.pp(thrown.message); } return thrownName + thrownMessage; } function messageDescription() { if (expected === null) { return ''; } else if (expected instanceof RegExp) { return ' with a message matching ' + matchersUtil.pp(expected); } else { return ' with message ' + matchersUtil.pp(expected); } } function matches(error) { return (errorType === null || error instanceof errorType) && (expected === null || messageMatch(error.message)); } return { match: function(thrown) { if (matches(thrown)) { return pass(function() { return 'Expected function not to throw ' + errorTypeDescription + messageDescription() + '.'; }); } else { return fail(function() { return 'Expected function to throw ' + errorTypeDescription + messageDescription() + ', but it threw ' + thrownDescription(thrown) + '.'; }); } } }; } function isStringOrRegExp(potential) { return potential instanceof RegExp || (typeof potential == 'string'); } function isAnErrorType(type) { if (typeof type !== 'function') { return false; } var Surrogate = function() {}; Surrogate.prototype = type.prototype; return j$.isError_(new Surrogate()); } } function pass(message) { return { pass: true, message: message }; } function fail(message) { return { pass: false, message: message }; } return toThrowError; }; getJasmineRequireObj().toThrowMatching = function(j$) { var usageError = j$.formatErrorMsg('', 'expect(function() {}).toThrowMatching()'); /** * {@link expect} a function to `throw` something matching a predicate. * @function * @name matchers#toThrowMatching * @since 3.0.0 * @param {Function} predicate - A function that takes the thrown exception as its parameter and returns true if it matches. * @example * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return thrown.message === 'nope'; }); */ function toThrowMatching(matchersUtil) { return { compare: function(actual, predicate) { var thrown; if (typeof actual !== 'function') { throw new Error(usageError('Actual is not a Function')); } if (typeof predicate !== 'function') { throw new Error(usageError('Predicate is not a Function')); } try { actual(); return fail('Expected function to throw an exception.'); } catch (e) { thrown = e; } if (predicate(thrown)) { return pass('Expected function not to throw an exception matching a predicate.'); } else { return fail(function() { return 'Expected function to throw an exception matching a predicate, ' + 'but it threw ' + thrownDescription(thrown) + '.'; }); } } }; function thrownDescription(thrown) { if (thrown && thrown.constructor) { return j$.fnNameFor(thrown.constructor) + ' with message ' + matchersUtil.pp(thrown.message); } else { return matchersUtil.pp(thrown); } } } function pass(message) { return { pass: true, message: message }; } function fail(message) { return { pass: false, message: message }; } return toThrowMatching; }; getJasmineRequireObj().MockDate = function() { function MockDate(global) { var self = this; var currentTime = 0; if (!global || !global.Date) { self.install = function() {}; self.tick = function() {}; self.uninstall = function() {}; return self; } var GlobalDate = global.Date; self.install = function(mockDate) { if (mockDate instanceof GlobalDate) { currentTime = mockDate.getTime(); } else { currentTime = new GlobalDate().getTime(); } global.Date = FakeDate; }; self.tick = function(millis) { millis = millis || 0; currentTime = currentTime + millis; }; self.uninstall = function() { currentTime = 0; global.Date = GlobalDate; }; createDateProperties(); return self; function FakeDate() { switch (arguments.length) { case 0: return new GlobalDate(currentTime); case 1: return new GlobalDate(arguments[0]); case 2: return new GlobalDate(arguments[0], arguments[1]); case 3: return new GlobalDate(arguments[0], arguments[1], arguments[2]); case 4: return new GlobalDate( arguments[0], arguments[1], arguments[2], arguments[3] ); case 5: return new GlobalDate( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4] ); case 6: return new GlobalDate( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5] ); default: return new GlobalDate( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6] ); } } function createDateProperties() { FakeDate.prototype = GlobalDate.prototype; FakeDate.now = function() { if (GlobalDate.now) { return currentTime; } else { throw new Error('Browser does not support Date.now()'); } }; FakeDate.toSource = GlobalDate.toSource; FakeDate.toString = GlobalDate.toString; FakeDate.parse = GlobalDate.parse; FakeDate.UTC = GlobalDate.UTC; } } return MockDate; }; getJasmineRequireObj().makePrettyPrinter = function(j$) { function SinglePrettyPrintRun(customObjectFormatters, pp) { this.customObjectFormatters_ = customObjectFormatters; this.ppNestLevel_ = 0; this.seen = []; this.length = 0; this.stringParts = []; this.pp_ = pp; } function hasCustomToString(value) { // value.toString !== Object.prototype.toString if value has no custom toString but is from another context (e.g. // iframe, web worker) try { return ( j$.isFunction_(value.toString) && value.toString !== Object.prototype.toString && value.toString() !== Object.prototype.toString.call(value) ); } catch (e) { // The custom toString() threw. return true; } } SinglePrettyPrintRun.prototype.format = function(value) { this.ppNestLevel_++; try { var customFormatResult = this.applyCustomFormatters_(value); if (customFormatResult) { this.emitScalar(customFormatResult); } else if (j$.util.isUndefined(value)) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); } else if (value === 0 && 1 / value === -Infinity) { this.emitScalar('-0'); } else if (value === j$.getGlobal()) { this.emitScalar(''); } else if (value.jasmineToString) { this.emitScalar(value.jasmineToString(this.pp_)); } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { this.emitScalar('spy on ' + value.and.identity); } else if (j$.isSpy(value.toString)) { this.emitScalar('spy on ' + value.toString.and.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); } else if (j$.isDomNode(value)) { if (value.tagName) { this.emitDomElement(value); } else { this.emitScalar('HTMLNode'); } } else if (value instanceof Date) { this.emitScalar('Date(' + value + ')'); } else if (j$.isSet(value)) { this.emitSet(value); } else if (j$.isMap(value)) { this.emitMap(value); } else if (j$.isTypedArray_(value)) { this.emitTypedArray(value); } else if ( value.toString && typeof value === 'object' && !j$.isArray_(value) && hasCustomToString(value) ) { try { this.emitScalar(value.toString()); } catch (e) { this.emitScalar('has-invalid-toString-method'); } } else if (j$.util.arrayContains(this.seen, value)) { this.emitScalar( '' ); } else if (j$.isArray_(value) || j$.isA_('Object', value)) { this.seen.push(value); if (j$.isArray_(value)) { this.emitArray(value); } else { this.emitObject(value); } this.seen.pop(); } else { this.emitScalar(value.toString()); } } catch (e) { if (this.ppNestLevel_ > 1 || !(e instanceof MaxCharsReachedError)) { throw e; } } finally { this.ppNestLevel_--; } }; SinglePrettyPrintRun.prototype.applyCustomFormatters_ = function(value) { return customFormat(value, this.customObjectFormatters_); }; SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) { var objKeys = keys(obj, j$.isArray_(obj)); var isGetter = function isGetter(prop) {}; if (obj.__lookupGetter__) { isGetter = function isGetter(prop) { var getter = obj.__lookupGetter__(prop); return !j$.util.isUndefined(getter) && getter !== null; }; } var length = Math.min(objKeys.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); for (var i = 0; i < length; i++) { var property = objKeys[i]; fn(property, isGetter(property)); } return objKeys.length > length; }; SinglePrettyPrintRun.prototype.emitScalar = function(value) { this.append(value); }; SinglePrettyPrintRun.prototype.emitString = function(value) { this.append("'" + value + "'"); }; SinglePrettyPrintRun.prototype.emitArray = function(array) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Array'); return; } var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); this.append('[ '); for (var i = 0; i < length; i++) { if (i > 0) { this.append(', '); } this.format(array[i]); } if (array.length > length) { this.append(', ...'); } var self = this; var first = array.length === 0; var truncated = this.iterateObject(array, function(property, isGetter) { if (first) { first = false; } else { self.append(', '); } self.formatProperty(array, property, isGetter); }); if (truncated) { this.append(', ...'); } this.append(' ]'); }; SinglePrettyPrintRun.prototype.emitSet = function(set) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Set'); return; } this.append('Set( '); var size = Math.min(set.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); var i = 0; set.forEach(function(value, key) { if (i >= size) { return; } if (i > 0) { this.append(', '); } this.format(value); i++; }, this); if (set.size > size) { this.append(', ...'); } this.append(' )'); }; SinglePrettyPrintRun.prototype.emitMap = function(map) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Map'); return; } this.append('Map( '); var size = Math.min(map.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); var i = 0; map.forEach(function(value, key) { if (i >= size) { return; } if (i > 0) { this.append(', '); } this.format([key, value]); i++; }, this); if (map.size > size) { this.append(', ...'); } this.append(' )'); }; SinglePrettyPrintRun.prototype.emitObject = function(obj) { var ctor = obj.constructor, constructorName; constructorName = typeof ctor === 'function' && obj instanceof ctor ? j$.fnNameFor(obj.constructor) : 'null'; this.append(constructorName); if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { return; } var self = this; this.append('({ '); var first = true; var truncated = this.iterateObject(obj, function(property, isGetter) { if (first) { first = false; } else { self.append(', '); } self.formatProperty(obj, property, isGetter); }); if (truncated) { this.append(', ...'); } this.append(' })'); }; SinglePrettyPrintRun.prototype.emitTypedArray = function(arr) { var constructorName = j$.fnNameFor(arr.constructor), limitedArray = Array.prototype.slice.call( arr, 0, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH ), itemsString = Array.prototype.join.call(limitedArray, ', '); if (limitedArray.length !== arr.length) { itemsString += ', ...'; } this.append(constructorName + ' [ ' + itemsString + ' ]'); }; SinglePrettyPrintRun.prototype.emitDomElement = function(el) { var tagName = el.tagName.toLowerCase(), attrs = el.attributes, i, len = attrs.length, out = '<' + tagName, attr; for (i = 0; i < len; i++) { attr = attrs[i]; out += ' ' + attr.name; if (attr.value !== '') { out += '="' + attr.value + '"'; } } out += '>'; if (el.childElementCount !== 0 || el.textContent !== '') { out += '...'; } this.append(out); }; SinglePrettyPrintRun.prototype.formatProperty = function( obj, property, isGetter ) { this.append(property); this.append(': '); if (isGetter) { this.append(''); } else { this.format(obj[property]); } }; SinglePrettyPrintRun.prototype.append = function(value) { // This check protects us from the rare case where an object has overriden // `toString()` with an invalid implementation (returning a non-string). if (typeof value !== 'string') { value = Object.prototype.toString.call(value); } var result = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length); this.length += result.value.length; this.stringParts.push(result.value); if (result.truncated) { throw new MaxCharsReachedError(); } }; function truncate(s, maxlen) { if (s.length <= maxlen) { return { value: s, truncated: false }; } s = s.substring(0, maxlen - 4) + ' ...'; return { value: s, truncated: true }; } function MaxCharsReachedError() { this.message = 'Exceeded ' + j$.MAX_PRETTY_PRINT_CHARS + ' characters while pretty-printing a value'; } MaxCharsReachedError.prototype = new Error(); function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { var keys = []; for (var key in o) { if (j$.util.has(o, key)) { keys.push(key); } } return keys; })(obj); if (!isArray) { return allKeys; } if (allKeys.length === 0) { return allKeys; } var extraKeys = []; for (var i = 0; i < allKeys.length; i++) { if (!/^[0-9]+$/.test(allKeys[i])) { extraKeys.push(allKeys[i]); } } return extraKeys; } function customFormat(value, customObjectFormatters) { var i, result; for (i = 0; i < customObjectFormatters.length; i++) { result = customObjectFormatters[i](value); if (result !== undefined) { return result; } } } return function(customObjectFormatters) { customObjectFormatters = customObjectFormatters || []; var pp = function(value) { var prettyPrinter = new SinglePrettyPrintRun(customObjectFormatters, pp); prettyPrinter.format(value); return prettyPrinter.stringParts.join(''); }; pp.customFormat_ = function(value) { return customFormat(value, customObjectFormatters); }; return pp; }; }; getJasmineRequireObj().QueueRunner = function(j$) { function StopExecutionError() {} StopExecutionError.prototype = new Error(); j$.StopExecutionError = StopExecutionError; function once(fn) { var called = false; return function(arg) { if (!called) { called = true; // Direct call using single parameter, because cleanup/next does not need more fn(arg); } return null; }; } function emptyFn() {} function QueueRunner(attrs) { var queueableFns = attrs.queueableFns || []; this.queueableFns = queueableFns.concat(attrs.cleanupFns || []); this.firstCleanupIx = queueableFns.length; this.onComplete = attrs.onComplete || emptyFn; this.clearStack = attrs.clearStack || function(fn) { fn(); }; this.onException = attrs.onException || emptyFn; this.userContext = attrs.userContext || new j$.UserContext(); this.timeout = attrs.timeout || { setTimeout: setTimeout, clearTimeout: clearTimeout }; this.fail = attrs.fail || emptyFn; this.globalErrors = attrs.globalErrors || { pushListener: emptyFn, popListener: emptyFn }; this.completeOnFirstError = !!attrs.completeOnFirstError; this.errored = false; if (typeof this.onComplete !== 'function') { throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete)); } this.deprecated = attrs.deprecated; } QueueRunner.prototype.execute = function() { var self = this; this.handleFinalError = function(message, source, lineno, colno, error) { // Older browsers would send the error as the first parameter. HTML5 // specifies the the five parameters above. The error instance should // be preffered, otherwise the call stack would get lost. self.onException(error || message); }; this.globalErrors.pushListener(this.handleFinalError); this.run(0); }; QueueRunner.prototype.skipToCleanup = function(lastRanIndex) { if (lastRanIndex < this.firstCleanupIx) { this.run(this.firstCleanupIx); } else { this.run(lastRanIndex + 1); } }; QueueRunner.prototype.clearTimeout = function(timeoutId) { Function.prototype.apply.apply(this.timeout.clearTimeout, [ j$.getGlobal(), [timeoutId] ]); }; QueueRunner.prototype.setTimeout = function(fn, timeout) { return Function.prototype.apply.apply(this.timeout.setTimeout, [ j$.getGlobal(), [fn, timeout] ]); }; QueueRunner.prototype.attempt = function attempt(iterativeIndex) { var self = this, completedSynchronously = true, handleError = function handleError(error) { onException(error); next(error); }, cleanup = once(function cleanup() { if (timeoutId !== void 0) { self.clearTimeout(timeoutId); } self.globalErrors.popListener(handleError); }), next = once(function next(err) { cleanup(); if (j$.isError_(err)) { if (!(err instanceof StopExecutionError) && !err.jasmineMessage) { self.fail(err); } self.errored = errored = true; } function runNext() { if (self.completeOnFirstError && errored) { self.skipToCleanup(iterativeIndex); } else { self.run(iterativeIndex + 1); } } if (completedSynchronously) { self.setTimeout(runNext); } else { runNext(); } }), errored = false, queueableFn = self.queueableFns[iterativeIndex], timeoutId; next.fail = function nextFail() { self.fail.apply(null, arguments); self.errored = errored = true; next(); }; self.globalErrors.pushListener(handleError); if (queueableFn.timeout !== undefined) { var timeoutInterval = queueableFn.timeout || j$.DEFAULT_TIMEOUT_INTERVAL; timeoutId = self.setTimeout(function() { var error = new Error( 'Timeout - Async function did not complete within ' + timeoutInterval + 'ms ' + (queueableFn.timeout ? '(custom timeout)' : '(set by jasmine.DEFAULT_TIMEOUT_INTERVAL)') ); onException(error); next(); }, timeoutInterval); } try { if (queueableFn.fn.length === 0) { var maybeThenable = queueableFn.fn.call(self.userContext); if (maybeThenable && j$.isFunction_(maybeThenable.then)) { maybeThenable.then(next, onPromiseRejection); completedSynchronously = false; return { completedSynchronously: false }; } } else { queueableFn.fn.call(self.userContext, next); completedSynchronously = false; return { completedSynchronously: false }; } } catch (e) { onException(e); self.errored = errored = true; } cleanup(); return { completedSynchronously: true, errored: errored }; function onException(e) { self.onException(e); self.errored = errored = true; } function onPromiseRejection(e) { onException(e); next(); } }; QueueRunner.prototype.run = function(recursiveIndex) { var length = this.queueableFns.length, self = this, iterativeIndex; for ( iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++ ) { var result = this.attempt(iterativeIndex); if (!result.completedSynchronously) { return; } self.errored = self.errored || result.errored; if (this.completeOnFirstError && result.errored) { this.skipToCleanup(iterativeIndex); return; } } this.clearStack(function() { self.globalErrors.popListener(self.handleFinalError); self.onComplete(self.errored && new StopExecutionError()); }); }; return QueueRunner; }; getJasmineRequireObj().ReportDispatcher = function(j$) { function ReportDispatcher(methods, queueRunnerFactory) { var dispatchedMethods = methods || []; for (var i = 0; i < dispatchedMethods.length; i++) { var method = dispatchedMethods[i]; this[method] = (function(m) { return function() { dispatch(m, arguments); }; })(method); } var reporters = []; var fallbackReporter = null; this.addReporter = function(reporter) { reporters.push(reporter); }; this.provideFallbackReporter = function(reporter) { fallbackReporter = reporter; }; this.clearReporters = function() { reporters = []; }; return this; function dispatch(method, args) { if (reporters.length === 0 && fallbackReporter !== null) { reporters.push(fallbackReporter); } var onComplete = args[args.length - 1]; args = j$.util.argsToArray(args).splice(0, args.length - 1); var fns = []; for (var i = 0; i < reporters.length; i++) { var reporter = reporters[i]; addFn(fns, reporter, method, args); } queueRunnerFactory({ queueableFns: fns, onComplete: onComplete, isReporter: true }); } function addFn(fns, reporter, method, args) { var fn = reporter[method]; if (!fn) { return; } var thisArgs = j$.util.cloneArgs(args); if (fn.length <= 1) { fns.push({ fn: function() { return fn.apply(reporter, thisArgs); } }); } else { fns.push({ fn: function(done) { return fn.apply(reporter, thisArgs.concat([done])); } }); } } } return ReportDispatcher; }; getJasmineRequireObj().interface = function(jasmine, env) { var jasmineInterface = { /** * Callback passed to parts of the Jasmine base interface. * * By default Jasmine assumes this function completes synchronously. * If you have code that you need to test asynchronously, you can declare that you receive a `done` callback, return a Promise, or use the `async` keyword if it is supported in your environment. * @callback implementationCallback * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. */ /** * Create a group of specs (often called a suite). * * Calls to `describe` can be nested within other calls to compose your suite as a tree. * @name describe * @since 1.3.0 * @function * @global * @param {String} description Textual description of the group * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and specs */ describe: function(description, specDefinitions) { return env.describe(description, specDefinitions); }, /** * A temporarily disabled [`describe`]{@link describe} * * Specs within an `xdescribe` will be marked pending and not executed * @name xdescribe * @since 1.3.0 * @function * @global * @param {String} description Textual description of the group * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and specs */ xdescribe: function(description, specDefinitions) { return env.xdescribe(description, specDefinitions); }, /** * A focused [`describe`]{@link describe} * * If suites or specs are focused, only those that are focused will be executed * @see fit * @name fdescribe * @since 2.1.0 * @function * @global * @param {String} description Textual description of the group * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and specs */ fdescribe: function(description, specDefinitions) { return env.fdescribe(description, specDefinitions); }, /** * Define a single spec. A spec should contain one or more {@link expect|expectations} that test the state of the code. * * A spec whose expectations all succeed will be passing and a spec with any failures will fail. * The name `it` is a pronoun for the test target, not an abbreviation of anything. It makes the * spec more readable by connecting the function name `it` and the argument `description` as a * complete sentence. * @name it * @since 1.3.0 * @function * @global * @param {String} description Textual description of what this spec is checking * @param {implementationCallback} [testFunction] Function that contains the code of your test. If not provided the test will be `pending`. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec. * @see async */ it: function() { return env.it.apply(env, arguments); }, /** * A temporarily disabled [`it`]{@link it} * * The spec will report as `pending` and will not be executed. * @name xit * @since 1.3.0 * @function * @global * @param {String} description Textual description of what this spec is checking. * @param {implementationCallback} [testFunction] Function that contains the code of your test. Will not be executed. */ xit: function() { return env.xit.apply(env, arguments); }, /** * A focused [`it`]{@link it} * * If suites or specs are focused, only those that are focused will be executed. * @name fit * @since 2.1.0 * @function * @global * @param {String} description Textual description of what this spec is checking. * @param {implementationCallback} testFunction Function that contains the code of your test. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec. * @see async */ fit: function() { return env.fit.apply(env, arguments); }, /** * Run some shared setup before each of the specs in the {@link describe} in which it is called. * @name beforeEach * @since 1.3.0 * @function * @global * @param {implementationCallback} [function] Function that contains the code to setup your specs. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeEach. * @see async */ beforeEach: function() { return env.beforeEach.apply(env, arguments); }, /** * Run some shared teardown after each of the specs in the {@link describe} in which it is called. * @name afterEach * @since 1.3.0 * @function * @global * @param {implementationCallback} [function] Function that contains the code to teardown your specs. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterEach. * @see async */ afterEach: function() { return env.afterEach.apply(env, arguments); }, /** * Run some shared setup once before all of the specs in the {@link describe} are run. * * _Note:_ Be careful, sharing the setup from a beforeAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail. * @name beforeAll * @since 2.1.0 * @function * @global * @param {implementationCallback} [function] Function that contains the code to setup your specs. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeAll. * @see async */ beforeAll: function() { return env.beforeAll.apply(env, arguments); }, /** * Run some shared teardown once after all of the specs in the {@link describe} are run. * * _Note:_ Be careful, sharing the teardown from a afterAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail. * @name afterAll * @since 2.1.0 * @function * @global * @param {implementationCallback} [function] Function that contains the code to teardown your specs. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterAll. * @see async */ afterAll: function() { return env.afterAll.apply(env, arguments); }, /** * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SpecResult} * @name setSpecProperty * @since 3.6.0 * @function * @param {String} key The name of the property * @param {*} value The value of the property */ setSpecProperty: function(key, value) { return env.setSpecProperty(key, value); }, /** * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SuiteResult} * @name setSuiteProperty * @since 3.6.0 * @function * @param {String} key The name of the property * @param {*} value The value of the property */ setSuiteProperty: function(key, value) { return env.setSuiteProperty(key, value); }, /** * Create an expectation for a spec. * @name expect * @since 1.3.0 * @function * @global * @param {Object} actual - Actual computed value to test expectations against. * @return {matchers} */ expect: function(actual) { return env.expect(actual); }, /** * Create an asynchronous expectation for a spec. Note that the matchers * that are provided by an asynchronous expectation all return promises * which must be either returned from the spec or waited for using `await` * in order for Jasmine to associate them with the correct spec. * @name expectAsync * @since 3.3.0 * @function * @global * @param {Object} actual - Actual computed value to test expectations against. * @return {async-matchers} * @example * await expectAsync(somePromise).toBeResolved(); * @example * return expectAsync(somePromise).toBeResolved(); */ expectAsync: function(actual) { return env.expectAsync(actual); }, /** * Mark a spec as pending, expectation results will be ignored. * @name pending * @since 2.0.0 * @function * @global * @param {String} [message] - Reason the spec is pending. */ pending: function() { return env.pending.apply(env, arguments); }, /** * Explicitly mark a spec as failed. * @name fail * @since 2.1.0 * @function * @global * @param {String|Error} [error] - Reason for the failure. */ fail: function() { return env.fail.apply(env, arguments); }, /** * Install a spy onto an existing object. * @name spyOn * @since 1.3.0 * @function * @global * @param {Object} obj - The object upon which to install the {@link Spy}. * @param {String} methodName - The name of the method to replace with a {@link Spy}. * @returns {Spy} */ spyOn: function(obj, methodName) { return env.spyOn(obj, methodName); }, /** * Install a spy on a property installed with `Object.defineProperty` onto an existing object. * @name spyOnProperty * @since 2.6.0 * @function * @global * @param {Object} obj - The object upon which to install the {@link Spy} * @param {String} propertyName - The name of the property to replace with a {@link Spy}. * @param {String} [accessType=get] - The access type (get|set) of the property to {@link Spy} on. * @returns {Spy} */ spyOnProperty: function(obj, methodName, accessType) { return env.spyOnProperty(obj, methodName, accessType); }, /** * Installs spies on all writable and configurable properties of an object. * @name spyOnAllFunctions * @since 3.2.1 * @function * @global * @param {Object} obj - The object upon which to install the {@link Spy}s * @returns {Object} the spied object */ spyOnAllFunctions: function(obj) { return env.spyOnAllFunctions(obj); }, jsApiReporter: new jasmine.JsApiReporter({ timer: new jasmine.Timer() }), /** * @namespace jasmine */ jasmine: jasmine }; /** * Add a custom equality tester for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addCustomEqualityTester * @since 2.0.0 * @function * @param {Function} tester - A function which takes two arguments to compare and returns a `true` or `false` comparison result if it knows how to compare them, and `undefined` otherwise. * @see custom_equality */ jasmine.addCustomEqualityTester = function(tester) { env.addCustomEqualityTester(tester); }; /** * Add custom matchers for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addMatchers * @since 2.0.0 * @function * @param {Object} matchers - Keys from this object will be the new matcher names. * @see custom_matcher */ jasmine.addMatchers = function(matchers) { return env.addMatchers(matchers); }; /** * Add custom async matchers for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addAsyncMatchers * @since 3.5.0 * @function * @param {Object} matchers - Keys from this object will be the new async matcher names. * @see custom_matcher */ jasmine.addAsyncMatchers = function(matchers) { return env.addAsyncMatchers(matchers); }; /** * Add a custom object formatter for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addCustomObjectFormatter * @since 3.6.0 * @function * @param {Function} formatter - A function which takes a value to format and returns a string if it knows how to format it, and `undefined` otherwise. * @see custom_object_formatter */ jasmine.addCustomObjectFormatter = function(formatter) { return env.addCustomObjectFormatter(formatter); }; /** * Get the currently booted mock {Clock} for this Jasmine environment. * @name jasmine.clock * @since 2.0.0 * @function * @returns {Clock} */ jasmine.clock = function() { return env.clock; }; /** * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. * @name jasmine.createSpy * @since 1.3.0 * @function * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. * @param {Function} [originalFn] - Function to act as the real implementation. * @return {Spy} */ jasmine.createSpy = function(name, originalFn) { return env.createSpy(name, originalFn); }; /** * Create an object with multiple {@link Spy}s as its members. * @name jasmine.createSpyObj * @since 1.3.0 * @function * @param {String} [baseName] - Base name for the spies in the object. * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. * @param {String[]|Object} [propertyNames] - Array of property names to create spies for, or Object whose keys will be propertynames and values the {@link Spy#and#returnValue|returnValue}. * @return {Object} */ jasmine.createSpyObj = function(baseName, methodNames, propertyNames) { return env.createSpyObj(baseName, methodNames, propertyNames); }; /** * Add a custom spy strategy for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addSpyStrategy * @since 3.5.0 * @function * @param {String} name - The name of the strategy (i.e. what you call from `and`) * @param {Function} factory - Factory function that returns the plan to be executed. */ jasmine.addSpyStrategy = function(name, factory) { return env.addSpyStrategy(name, factory); }; /** * Set the default spy strategy for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.setDefaultSpyStrategy * @function * @param {Function} defaultStrategyFn - a function that assigns a strategy * @example * beforeEach(function() { * jasmine.setDefaultSpyStrategy(and => and.returnValue(true)); * }); */ jasmine.setDefaultSpyStrategy = function(defaultStrategyFn) { return env.setDefaultSpyStrategy(defaultStrategyFn); }; return jasmineInterface; }; getJasmineRequireObj().Spy = function(j$) { var nextOrder = (function() { var order = 0; return function() { return order++; }; })(); var matchersUtil = new j$.MatchersUtil({ customTesters: [], pp: j$.makePrettyPrinter() }); /** * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} * @constructor * @name Spy */ function Spy( name, originalFn, customStrategies, defaultStrategyFn, getPromise ) { var numArgs = typeof originalFn === 'function' ? originalFn.length : 0, wrapper = makeFunc(numArgs, function(context, args, invokeNew) { return spy(context, args, invokeNew); }), strategyDispatcher = new SpyStrategyDispatcher({ name: name, fn: originalFn, getSpy: function() { return wrapper; }, customStrategies: customStrategies, getPromise: getPromise }), callTracker = new j$.CallTracker(), spy = function(context, args, invokeNew) { /** * @name Spy.callData * @property {object} object - `this` context for the invocation. * @property {number} invocationOrder - Order of the invocation. * @property {Array} args - The arguments passed for this invocation. */ var callData = { object: context, invocationOrder: nextOrder(), args: Array.prototype.slice.apply(args) }; callTracker.track(callData); var returnValue = strategyDispatcher.exec(context, args, invokeNew); callData.returnValue = returnValue; return returnValue; }; function makeFunc(length, fn) { switch (length) { case 1: return function wrap1(a) { return fn(this, arguments, this instanceof wrap1); }; case 2: return function wrap2(a, b) { return fn(this, arguments, this instanceof wrap2); }; case 3: return function wrap3(a, b, c) { return fn(this, arguments, this instanceof wrap3); }; case 4: return function wrap4(a, b, c, d) { return fn(this, arguments, this instanceof wrap4); }; case 5: return function wrap5(a, b, c, d, e) { return fn(this, arguments, this instanceof wrap5); }; case 6: return function wrap6(a, b, c, d, e, f) { return fn(this, arguments, this instanceof wrap6); }; case 7: return function wrap7(a, b, c, d, e, f, g) { return fn(this, arguments, this instanceof wrap7); }; case 8: return function wrap8(a, b, c, d, e, f, g, h) { return fn(this, arguments, this instanceof wrap8); }; case 9: return function wrap9(a, b, c, d, e, f, g, h, i) { return fn(this, arguments, this instanceof wrap9); }; default: return function wrap() { return fn(this, arguments, this instanceof wrap); }; } } for (var prop in originalFn) { if (prop === 'and' || prop === 'calls') { throw new Error( "Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon" ); } wrapper[prop] = originalFn[prop]; } /** * @member {SpyStrategy} - Accesses the default strategy for the spy. This strategy will be used * whenever the spy is called with arguments that don't match any strategy * created with {@link Spy#withArgs}. * @name Spy#and * @since 2.0.0 * @example * spyOn(someObj, 'func').and.returnValue(42); */ wrapper.and = strategyDispatcher.and; /** * Specifies a strategy to be used for calls to the spy that have the * specified arguments. * @name Spy#withArgs * @since 3.0.0 * @function * @param {...*} args - The arguments to match * @type {SpyStrategy} * @example * spyOn(someObj, 'func').withArgs(1, 2, 3).and.returnValue(42); * someObj.func(1, 2, 3); // returns 42 */ wrapper.withArgs = function() { return strategyDispatcher.withArgs.apply(strategyDispatcher, arguments); }; wrapper.calls = callTracker; if (defaultStrategyFn) { defaultStrategyFn(wrapper.and); } return wrapper; } function SpyStrategyDispatcher(strategyArgs) { var baseStrategy = new j$.SpyStrategy(strategyArgs); var argsStrategies = new StrategyDict(function() { return new j$.SpyStrategy(strategyArgs); }); this.and = baseStrategy; this.exec = function(spy, args, invokeNew) { var strategy = argsStrategies.get(args); if (!strategy) { if (argsStrategies.any() && !baseStrategy.isConfigured()) { throw new Error( "Spy '" + strategyArgs.name + "' received a call with arguments " + j$.pp(Array.prototype.slice.call(args)) + ' but all configured strategies specify other arguments.' ); } else { strategy = baseStrategy; } } return strategy.exec(spy, args, invokeNew); }; this.withArgs = function() { return { and: argsStrategies.getOrCreate(arguments) }; }; } function StrategyDict(strategyFactory) { this.strategies = []; this.strategyFactory = strategyFactory; } StrategyDict.prototype.any = function() { return this.strategies.length > 0; }; StrategyDict.prototype.getOrCreate = function(args) { var strategy = this.get(args); if (!strategy) { strategy = this.strategyFactory(); this.strategies.push({ args: args, strategy: strategy }); } return strategy; }; StrategyDict.prototype.get = function(args) { var i; for (i = 0; i < this.strategies.length; i++) { if (matchersUtil.equals(args, this.strategies[i].args)) { return this.strategies[i].strategy; } } }; return Spy; }; getJasmineRequireObj().SpyFactory = function(j$) { function SpyFactory(getCustomStrategies, getDefaultStrategyFn, getPromise) { var self = this; this.createSpy = function(name, originalFn) { return j$.Spy( name, originalFn, getCustomStrategies(), getDefaultStrategyFn(), getPromise ); }; this.createSpyObj = function(baseName, methodNames, propertyNames) { var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); if (baseNameIsCollection) { propertyNames = methodNames; methodNames = baseName; baseName = 'unknown'; } var obj = {}; var spy, descriptor; var methods = normalizeKeyValues(methodNames); for (var i = 0; i < methods.length; i++) { spy = obj[methods[i][0]] = self.createSpy( baseName + '.' + methods[i][0] ); if (methods[i].length > 1) { spy.and.returnValue(methods[i][1]); } } var properties = normalizeKeyValues(propertyNames); for (var i = 0; i < properties.length; i++) { descriptor = { get: self.createSpy(baseName + '.' + properties[i][0] + '.get'), set: self.createSpy(baseName + '.' + properties[i][0] + '.set') }; if (properties[i].length > 1) { descriptor.get.and.returnValue(properties[i][1]); descriptor.set.and.returnValue(properties[i][1]); } Object.defineProperty(obj, properties[i][0], descriptor); } if (methods.length === 0 && properties.length === 0) { throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; } return obj; }; } function normalizeKeyValues(object) { var result = []; if (j$.isArray_(object)) { for (var i = 0; i < object.length; i++) { result.push([object[i]]); } } else if (j$.isObject_(object)) { for (var key in object) { if (object.hasOwnProperty(key)) { result.push([key, object[key]]); } } } return result; } return SpyFactory; }; getJasmineRequireObj().SpyRegistry = function(j$) { var spyOnMsg = j$.formatErrorMsg('', 'spyOn(, )'); var spyOnPropertyMsg = j$.formatErrorMsg( '', 'spyOnProperty(, , [accessType])' ); function SpyRegistry(options) { options = options || {}; var global = options.global || j$.getGlobal(); var createSpy = options.createSpy; var currentSpies = options.currentSpies || function() { return []; }; this.allowRespy = function(allow) { this.respy = allow; }; this.spyOn = function(obj, methodName) { var getErrorMsg = spyOnMsg; if (j$.util.isUndefined(obj) || obj === null) { throw new Error( getErrorMsg( 'could not find an object to spy upon for ' + methodName + '()' ) ); } if (j$.util.isUndefined(methodName) || methodName === null) { throw new Error(getErrorMsg('No method name supplied')); } if (j$.util.isUndefined(obj[methodName])) { throw new Error(getErrorMsg(methodName + '() method does not exist')); } if (obj[methodName] && j$.isSpy(obj[methodName])) { if (this.respy) { return obj[methodName]; } else { throw new Error( getErrorMsg(methodName + ' has already been spied upon') ); } } var descriptor = Object.getOwnPropertyDescriptor(obj, methodName); if (descriptor && !(descriptor.writable || descriptor.set)) { throw new Error( getErrorMsg(methodName + ' is not declared writable or has no setter') ); } var originalMethod = obj[methodName], spiedMethod = createSpy(methodName, originalMethod), restoreStrategy; if ( Object.prototype.hasOwnProperty.call(obj, methodName) || (obj === global && methodName === 'onerror') ) { restoreStrategy = function() { obj[methodName] = originalMethod; }; } else { restoreStrategy = function() { if (!delete obj[methodName]) { obj[methodName] = originalMethod; } }; } currentSpies().push({ restoreObjectToOriginalState: restoreStrategy }); obj[methodName] = spiedMethod; return spiedMethod; }; this.spyOnProperty = function(obj, propertyName, accessType) { var getErrorMsg = spyOnPropertyMsg; accessType = accessType || 'get'; if (j$.util.isUndefined(obj)) { throw new Error( getErrorMsg( 'spyOn could not find an object to spy upon for ' + propertyName + '' ) ); } if (j$.util.isUndefined(propertyName)) { throw new Error(getErrorMsg('No property name supplied')); } var descriptor = j$.util.getPropertyDescriptor(obj, propertyName); if (!descriptor) { throw new Error(getErrorMsg(propertyName + ' property does not exist')); } if (!descriptor.configurable) { throw new Error( getErrorMsg(propertyName + ' is not declared configurable') ); } if (!descriptor[accessType]) { throw new Error( getErrorMsg( 'Property ' + propertyName + ' does not have access type ' + accessType ) ); } if (j$.isSpy(descriptor[accessType])) { if (this.respy) { return descriptor[accessType]; } else { throw new Error( getErrorMsg( propertyName + '#' + accessType + ' has already been spied upon' ) ); } } var originalDescriptor = j$.util.clone(descriptor), spy = createSpy(propertyName, descriptor[accessType]), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { restoreStrategy = function() { Object.defineProperty(obj, propertyName, originalDescriptor); }; } else { restoreStrategy = function() { delete obj[propertyName]; }; } currentSpies().push({ restoreObjectToOriginalState: restoreStrategy }); descriptor[accessType] = spy; Object.defineProperty(obj, propertyName, descriptor); return spy; }; this.spyOnAllFunctions = function(obj) { if (j$.util.isUndefined(obj)) { throw new Error( 'spyOnAllFunctions could not find an object to spy upon' ); } var pointer = obj, props = [], prop, descriptor; while (pointer) { for (prop in pointer) { if ( Object.prototype.hasOwnProperty.call(pointer, prop) && pointer[prop] instanceof Function ) { descriptor = Object.getOwnPropertyDescriptor(pointer, prop); if ( (descriptor.writable || descriptor.set) && descriptor.configurable ) { props.push(prop); } } } pointer = Object.getPrototypeOf(pointer); } for (var i = 0; i < props.length; i++) { this.spyOn(obj, props[i]); } return obj; }; this.clearSpies = function() { var spies = currentSpies(); for (var i = spies.length - 1; i >= 0; i--) { var spyEntry = spies[i]; spyEntry.restoreObjectToOriginalState(); } }; } return SpyRegistry; }; getJasmineRequireObj().SpyStrategy = function(j$) { /** * @interface SpyStrategy */ function SpyStrategy(options) { options = options || {}; var self = this; /** * Get the identifying information for the spy. * @name SpyStrategy#identity * @since 3.0.0 * @member * @type {String} */ this.identity = options.name || 'unknown'; this.originalFn = options.fn || function() {}; this.getSpy = options.getSpy || function() {}; this.plan = this._defaultPlan = function() {}; var k, cs = options.customStrategies || {}; for (k in cs) { if (j$.util.has(cs, k) && !this[k]) { this[k] = createCustomPlan(cs[k]); } } var getPromise = typeof options.getPromise === 'function' ? options.getPromise : function() {}; var requirePromise = function(name) { var Promise = getPromise(); if (!Promise) { throw new Error( name + ' requires global Promise, or `Promise` configured with `jasmine.getEnv().configure()`' ); } return Promise; }; /** * Tell the spy to return a promise resolving to the specified value when invoked. * @name SpyStrategy#resolveTo * @since 3.5.0 * @function * @param {*} value The value to return. */ this.resolveTo = function(value) { var Promise = requirePromise('resolveTo'); self.plan = function() { return Promise.resolve(value); }; return self.getSpy(); }; /** * Tell the spy to return a promise rejecting with the specified value when invoked. * @name SpyStrategy#rejectWith * @since 3.5.0 * @function * @param {*} value The value to return. */ this.rejectWith = function(value) { var Promise = requirePromise('rejectWith'); self.plan = function() { return Promise.reject(value); }; return self.getSpy(); }; } function createCustomPlan(factory) { return function() { var plan = factory.apply(null, arguments); if (!j$.isFunction_(plan)) { throw new Error('Spy strategy must return a function'); } this.plan = plan; return this.getSpy(); }; } /** * Execute the current spy strategy. * @name SpyStrategy#exec * @since 2.0.0 * @function */ SpyStrategy.prototype.exec = function(context, args, invokeNew) { var contextArgs = [context].concat( args ? Array.prototype.slice.call(args) : [] ); var target = this.plan.bind.apply(this.plan, contextArgs); return invokeNew ? new target() : target(); }; /** * Tell the spy to call through to the real implementation when invoked. * @name SpyStrategy#callThrough * @since 2.0.0 * @function */ SpyStrategy.prototype.callThrough = function() { this.plan = this.originalFn; return this.getSpy(); }; /** * Tell the spy to return the value when invoked. * @name SpyStrategy#returnValue * @since 2.0.0 * @function * @param {*} value The value to return. */ SpyStrategy.prototype.returnValue = function(value) { this.plan = function() { return value; }; return this.getSpy(); }; /** * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. * @name SpyStrategy#returnValues * @since 2.1.0 * @function * @param {...*} values - Values to be returned on subsequent calls to the spy. */ SpyStrategy.prototype.returnValues = function() { var values = Array.prototype.slice.call(arguments); this.plan = function() { return values.shift(); }; return this.getSpy(); }; /** * Tell the spy to throw an error when invoked. * @name SpyStrategy#throwError * @since 2.0.0 * @function * @param {Error|Object|String} something Thing to throw */ SpyStrategy.prototype.throwError = function(something) { var error = j$.isString_(something) ? new Error(something) : something; this.plan = function() { throw error; }; return this.getSpy(); }; /** * Tell the spy to call a fake implementation when invoked. * @name SpyStrategy#callFake * @since 2.0.0 * @function * @param {Function} fn The function to invoke with the passed parameters. */ SpyStrategy.prototype.callFake = function(fn) { if (!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) { throw new Error( 'Argument passed to callFake should be a function, got ' + fn ); } this.plan = fn; return this.getSpy(); }; /** * Tell the spy to do nothing when invoked. This is the default. * @name SpyStrategy#stub * @since 2.0.0 * @function */ SpyStrategy.prototype.stub = function(fn) { this.plan = function() {}; return this.getSpy(); }; SpyStrategy.prototype.isConfigured = function() { return this.plan !== this._defaultPlan; }; return SpyStrategy; }; getJasmineRequireObj().StackTrace = function(j$) { function StackTrace(error) { var lines = error.stack.split('\n').filter(function(line) { return line !== ''; }); var extractResult = extractMessage(error.message, lines); if (extractResult) { this.message = extractResult.message; lines = extractResult.remainder; } var parseResult = tryParseFrames(lines); this.frames = parseResult.frames; this.style = parseResult.style; } var framePatterns = [ // PhantomJS on Linux, Node, Chrome, IE, Edge // e.g. " at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)" // Note that the "function name" can include a surprisingly large set of // characters, including angle brackets and square brackets. { re: /^\s*at ([^\)]+) \(([^\)]+)\)$/, fnIx: 1, fileLineColIx: 2, style: 'v8' }, // NodeJS alternate form, often mixed in with the Chrome style // e.g. " at /some/path:4320:20 { re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' }, // PhantomJS on OS X, Safari, Firefox // e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27" // or "http://localhost:8888/__jasmine__/jasmine.js:4320:27" { re: /^(([^@\s]+)@)?([^\s]+)$/, fnIx: 2, fileLineColIx: 3, style: 'webkit' } ]; // regexes should capture the function name (if any) as group 1 // and the file, line, and column as group 2. function tryParseFrames(lines) { var style = null; var frames = lines.map(function(line) { var convertedLine = first(framePatterns, function(pattern) { var overallMatch = line.match(pattern.re), fileLineColMatch; if (!overallMatch) { return null; } fileLineColMatch = overallMatch[pattern.fileLineColIx].match( /^(.*):(\d+):\d+$/ ); if (!fileLineColMatch) { return null; } style = style || pattern.style; return { raw: line, file: fileLineColMatch[1], line: parseInt(fileLineColMatch[2], 10), func: overallMatch[pattern.fnIx] }; }); return convertedLine || { raw: line }; }); return { style: style, frames: frames }; } function first(items, fn) { var i, result; for (i = 0; i < items.length; i++) { result = fn(items[i]); if (result) { return result; } } } function extractMessage(message, stackLines) { var len = messagePrefixLength(message, stackLines); if (len > 0) { return { message: stackLines.slice(0, len).join('\n'), remainder: stackLines.slice(len) }; } } function messagePrefixLength(message, stackLines) { if (!stackLines[0].match(/^\w*Error/)) { return 0; } var messageLines = message.split('\n'); var i; for (i = 1; i < messageLines.length; i++) { if (messageLines[i] !== stackLines[i]) { return 0; } } return messageLines.length; } return StackTrace; }; getJasmineRequireObj().Suite = function(j$) { function Suite(attrs) { this.env = attrs.env; this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.expectationResultFactory = attrs.expectationResultFactory; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; this.beforeFns = []; this.afterFns = []; this.beforeAllFns = []; this.afterAllFns = []; this.timer = attrs.timer || new j$.Timer(); this.children = []; /** * @typedef SuiteResult * @property {Int} id - The unique id of this suite. * @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} fullName - The full description including all ancestors of this suite. * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. * @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite. * @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach. * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty} */ this.result = { id: this.id, description: this.description, fullName: this.getFullName(), failedExpectations: [], deprecationWarnings: [], duration: null, properties: null }; } Suite.prototype.setSuiteProperty = function(key, value) { this.result.properties = this.result.properties || {}; this.result.properties[key] = value; }; Suite.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; Suite.prototype.expectAsync = function(actual) { return this.asyncExpectationFactory(actual, this); }; Suite.prototype.getFullName = function() { var fullName = []; for ( var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite ) { if (parentSuite.parentSuite) { fullName.unshift(parentSuite.description); } } return fullName.join(' '); }; Suite.prototype.pend = function() { this.markedPending = true; }; Suite.prototype.beforeEach = function(fn) { this.beforeFns.unshift(fn); }; Suite.prototype.beforeAll = function(fn) { this.beforeAllFns.push(fn); }; Suite.prototype.afterEach = function(fn) { this.afterFns.unshift(fn); }; Suite.prototype.afterAll = function(fn) { this.afterAllFns.unshift(fn); }; Suite.prototype.startTimer = function() { this.timer.start(); }; Suite.prototype.endTimer = function() { this.result.duration = this.timer.elapsed(); }; function removeFns(queueableFns) { for (var i = 0; i < queueableFns.length; i++) { queueableFns[i].fn = null; } } Suite.prototype.cleanupBeforeAfter = function() { removeFns(this.beforeAllFns); removeFns(this.afterAllFns); removeFns(this.beforeFns); removeFns(this.afterFns); }; Suite.prototype.addChild = function(child) { this.children.push(child); }; Suite.prototype.status = function() { if (this.markedPending) { return 'pending'; } if (this.result.failedExpectations.length > 0) { return 'failed'; } else { return 'passed'; } }; Suite.prototype.canBeReentered = function() { return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; }; Suite.prototype.getResult = function() { this.result.status = this.status(); return this.result; }; Suite.prototype.sharedUserContext = function() { if (!this.sharedContext) { this.sharedContext = this.parentSuite ? this.parentSuite.clonedSharedUserContext() : new j$.UserContext(); } return this.sharedContext; }; Suite.prototype.clonedSharedUserContext = function() { return j$.UserContext.fromExisting(this.sharedUserContext()); }; Suite.prototype.onException = function() { if (arguments[0] instanceof j$.errors.ExpectationFailed) { return; } var data = { matcherName: '', passed: false, expected: '', actual: '', error: arguments[0] }; var failedExpectation = this.expectationResultFactory(data); if (!this.parentSuite) { failedExpectation.globalErrorType = 'afterAll'; } this.result.failedExpectations.push(failedExpectation); }; Suite.prototype.addExpectationResult = function() { if (isFailure(arguments)) { var data = arguments[1]; this.result.failedExpectations.push(this.expectationResultFactory(data)); if (this.throwOnExpectationFailure) { throw new j$.errors.ExpectationFailed(); } } }; Suite.prototype.addDeprecationWarning = function(deprecation) { if (typeof deprecation === 'string') { deprecation = { message: deprecation }; } this.result.deprecationWarnings.push( this.expectationResultFactory(deprecation) ); }; function isFailure(args) { return !args[0]; } return Suite; }; if (typeof window == void 0 && typeof exports == 'object') { /* globals exports */ exports.Suite = jasmineRequire.Suite; } getJasmineRequireObj().Timer = function() { var defaultNow = (function(Date) { return function() { return new Date().getTime(); }; })(Date); function Timer(options) { options = options || {}; var now = options.now || defaultNow, startTime; this.start = function() { startTime = now(); }; this.elapsed = function() { return now() - startTime; }; } return Timer; }; getJasmineRequireObj().TreeProcessor = function() { function TreeProcessor(attrs) { var tree = attrs.tree, runnableIds = attrs.runnableIds, queueRunnerFactory = attrs.queueRunnerFactory, nodeStart = attrs.nodeStart || function() {}, nodeComplete = attrs.nodeComplete || function() {}, failSpecWithNoExpectations = !!attrs.failSpecWithNoExpectations, orderChildren = attrs.orderChildren || function(node) { return node.children; }, excludeNode = attrs.excludeNode || function(node) { return false; }, stats = { valid: true }, processed = false, defaultMin = Infinity, defaultMax = 1 - Infinity; this.processTree = function() { processNode(tree, true); processed = true; return stats; }; this.execute = function(done) { if (!processed) { this.processTree(); } if (!stats.valid) { throw 'invalid order'; } var childFns = wrapChildren(tree, 0); queueRunnerFactory({ queueableFns: childFns, userContext: tree.sharedUserContext(), onException: function() { tree.onException.apply(tree, arguments); }, onComplete: done }); }; function runnableIndex(id) { for (var i = 0; i < runnableIds.length; i++) { if (runnableIds[i] === id) { return i; } } } function processNode(node, parentExcluded) { var executableIndex = runnableIndex(node.id); if (executableIndex !== undefined) { parentExcluded = false; } if (!node.children) { var excluded = parentExcluded || excludeNode(node); stats[node.id] = { excluded: excluded, willExecute: !excluded && !node.markedPending, segments: [ { index: 0, owner: node, nodes: [node], min: startingMin(executableIndex), max: startingMax(executableIndex) } ] }; } else { var hasExecutableChild = false; var orderedChildren = orderChildren(node); for (var i = 0; i < orderedChildren.length; i++) { var child = orderedChildren[i]; processNode(child, parentExcluded); if (!stats.valid) { return; } var childStats = stats[child.id]; hasExecutableChild = hasExecutableChild || childStats.willExecute; } stats[node.id] = { excluded: parentExcluded, willExecute: hasExecutableChild }; segmentChildren(node, orderedChildren, stats[node.id], executableIndex); if (!node.canBeReentered() && stats[node.id].segments.length > 1) { stats = { valid: false }; } } } function startingMin(executableIndex) { return executableIndex === undefined ? defaultMin : executableIndex; } function startingMax(executableIndex) { return executableIndex === undefined ? defaultMax : executableIndex; } function segmentChildren( node, orderedChildren, nodeStats, executableIndex ) { var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, result = [currentSegment], lastMax = defaultMax, orderedChildSegments = orderChildSegments(orderedChildren); function isSegmentBoundary(minIndex) { return ( lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1 ); } for (var i = 0; i < orderedChildSegments.length; i++) { var childSegment = orderedChildSegments[i], maxIndex = childSegment.max, minIndex = childSegment.min; if (isSegmentBoundary(minIndex)) { currentSegment = { index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax }; result.push(currentSegment); } currentSegment.nodes.push(childSegment); currentSegment.min = Math.min(currentSegment.min, minIndex); currentSegment.max = Math.max(currentSegment.max, maxIndex); lastMax = maxIndex; } nodeStats.segments = result; } function orderChildSegments(children) { var specifiedOrder = [], unspecifiedOrder = []; for (var i = 0; i < children.length; i++) { var child = children[i], segments = stats[child.id].segments; for (var j = 0; j < segments.length; j++) { var seg = segments[j]; if (seg.min === defaultMin) { unspecifiedOrder.push(seg); } else { specifiedOrder.push(seg); } } } specifiedOrder.sort(function(a, b) { return a.min - b.min; }); return specifiedOrder.concat(unspecifiedOrder); } function executeNode(node, segmentNumber) { if (node.children) { return { fn: function(done) { var onStart = { fn: function(next) { nodeStart(node, next); } }; queueRunnerFactory({ onComplete: function() { var args = Array.prototype.slice.call(arguments, [0]); node.cleanupBeforeAfter(); nodeComplete(node, node.getResult(), function() { done.apply(undefined, args); }); }, queueableFns: [onStart].concat(wrapChildren(node, segmentNumber)), userContext: node.sharedUserContext(), onException: function() { node.onException.apply(node, arguments); } }); } }; } else { return { fn: function(done) { node.execute( done, stats[node.id].excluded, failSpecWithNoExpectations ); } }; } } function wrapChildren(node, segmentNumber) { var result = [], segmentChildren = stats[node.id].segments[segmentNumber].nodes; for (var i = 0; i < segmentChildren.length; i++) { result.push( executeNode(segmentChildren[i].owner, segmentChildren[i].index) ); } if (!stats[node.id].willExecute) { return result; } return node.beforeAllFns.concat(result).concat(node.afterAllFns); } } return TreeProcessor; }; getJasmineRequireObj().UserContext = function(j$) { function UserContext() {} UserContext.fromExisting = function(oldContext) { var context = new UserContext(); for (var prop in oldContext) { if (oldContext.hasOwnProperty(prop)) { context[prop] = oldContext[prop]; } } return context; }; return UserContext; }; getJasmineRequireObj().version = function() { return '3.6.0'; }; GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/jasmine.test.in000066400000000000000000000001241460766671100301140ustar00rootroot00000000000000[Test] Type=session Exec=@jasmine_path@ @installed_tests_execdir@/@name@ Output=TAP GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/jasmine.test.in.license000066400000000000000000000001651460766671100315420ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/meson.build000066400000000000000000000036121460766671100273270ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later installed_tests_execdir = join_paths(libexecdir, 'installed-tests', meson.project_name()) installed_tests_metadir = join_paths(datadir, 'installed-tests', meson.project_name()) installed_tests_srcdir = meson.current_source_dir() installed_tests_builddir = meson.current_build_dir() installed_tests_gjspath = join_paths(installed_tests_builddir, 'src') # meson Test Environment test_env = environment() test_env.set('GSCONNECT_TEST', '1') test_env.set('GJS_PATH', installed_tests_gjspath) test_env.set('NO_AT_BRIDGE', '1') test_env.set('GSETTINGS_BACKEND', 'memory') test_env.set('GSETTINGS_SCHEMA_DIR', installed_tests_builddir) test_env.set('G_DEBUG', 'fatal-warnings,fatal-criticals') test_config_js = configure_file( input: './config.js.in', output: 'config.js', configuration: extconfig, ) installed_tests_prepared = custom_target( 'prepare-tests', build_by_default: true, command: [ env_util, 'MESON_BUILD_ROOT=' + meson.project_build_root(), 'MESON_SOURCE_ROOT=' + meson.project_source_root(), 'G_TEST_BUILDDIR=' + installed_tests_builddir, 'G_TEST_SRCDIR=' + installed_tests_srcdir, join_paths(installed_tests_srcdir, 'prepare-tests.sh') ], output: 'none', ) # Bundled Jasmine runner minijasmine = find_program('minijasmine') minijasmine_path = join_paths(installed_tests_execdir, 'minijasmine') if get_option('installed_tests') install_data('jasmine.js', 'minijasmine', install_dir: installed_tests_execdir ) install_subdir('data', install_dir: installed_tests_execdir, ) install_subdir('fixtures', install_dir: installed_tests_execdir, ) install_data(test_config_js, install_dir: installed_tests_execdir, ) endif subdir('suites') GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/minijasmine000077500000000000000000000075771460766671100274340ustar00rootroot00000000000000#!/usr/bin/env -S gjs -m // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import system from 'system'; import {getJasmineRequireObj} from './jasmine.js'; // Utils function _formatMessage(message) { return message.replace(/\n/g, '\\n'); } function _formatStack(stack) { if (!stack) return '# No stack'; return stack.split('\n') .filter(line => line.indexOf('') === -1) .filter(line => line.indexOf('minijasmine') === -1) .map(line => `# ${line}`) .join('\n'); } /** * Reporter that outputs according to the Test Anything Protocol * See http://testanything.org/tap-specification.html */ class TapReporter { constructor() { this._failedSuites = []; this._specCount = 0; } jasmineStarted(info) { print(`1..${info.totalSpecsDefined}`); } jasmineDone() { this._failedSuites.forEach(failure => { failure.failedExpectations.forEach(result => { print('not ok - An error was thrown outside a test'); print(`# ${result.message}`); }); }); globalThis._jasmineMain.quit(); } suiteDone(result) { if (result.failedExpectations && result.failedExpectations.length > 0) { globalThis._jasmineRetval = 1; this._failedSuites.push(result); } if (result.status === 'disabled') print('# Suite was disabled:', result.fullName); } specStarted() { this._specCount++; } specDone(result) { let tapReport = 'ok'; if (result.status === 'failed') { globalThis._jasmineRetval = 1; tapReport = 'not ok'; } tapReport += ` ${this._specCount} ${result.fullName}`; if (result.status === 'pending' || result.status === 'disabled') tapReport += ` # SKIP ${result.pendingReason || result.status}`; print(tapReport); // Print additional diagnostic info on failure if (result.status === 'failed' && result.failedExpectations) { result.failedExpectations.forEach(failedExpectation => { print('# Message:', _formatMessage(failedExpectation.message)); print(`# Stack:\n${_formatStack(failedExpectation.stack)}`); }); } } } /** * Initialize Jasmine */ function initJasmine() { // Load Jasmine const jasmineRequire = getJasmineRequireObj(); const jasmineCore = jasmineRequire.core(jasmineRequire); globalThis._jasmineEnv = jasmineCore.getEnv(); globalThis._jasmineMain = GLib.MainLoop.new(null, false); globalThis._jasmineRetval = 0; // Install Jasmine API on the global object const iface = jasmineRequire.interface(jasmineCore, globalThis._jasmineEnv); Object.assign(globalThis, iface); // Register the TAP reporter globalThis._jasmineEnv.addReporter(new TapReporter()); } /** * Initialize test suites */ async function initTests() { // Load the test suite await import(`file://${ARGV[0]}`); } /** * Run initialized tests */ async function runTests() { GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { try { globalThis._jasmineEnv.execute(); } catch (e) { print('Bail out! Exception occurred inside Jasmine:', e); globalThis._jasmineRetval = 1; globalThis._jasmineMain.quit(); } return GLib.SOURCE_REMOVE; }); await globalThis._jasmineMain.runAsync(); // Exhaust the event loop const context = GLib.MainContext.default(); while (context.iteration(false)) continue; // Return the exit status system.exit(globalThis._jasmineRetval); } // TODO: add CLI options for isolated XDG, DBus, etc initJasmine(); await initTests(); await runTests(); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/prepare-tests.sh000077500000000000000000000015051460766671100303210ustar00rootroot00000000000000#!/usr/bin/env bash # SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later APP_ID="org.gnome.Shell.Extensions.GSConnect" #G_TEST_BUILDDIR=${MESON_BUILD_ROOT}/installed-tests # Copy source files cp -R ${MESON_SOURCE_ROOT}/src ${G_TEST_BUILDDIR} cp ${G_TEST_BUILDDIR}/config.js ${G_TEST_BUILDDIR}/src # Compile GResources glib-compile-resources --external-data \ --sourcedir=${MESON_BUILD_ROOT}/data \ --sourcedir=${MESON_SOURCE_ROOT}/data \ --target=${G_TEST_BUILDDIR}/src/${APP_ID}.gresource \ ${MESON_SOURCE_ROOT}/data/${APP_ID}.gresource.xml # Compile GSettings Schema glib-compile-schemas --targetdir=${G_TEST_BUILDDIR} \ ${MESON_SOURCE_ROOT}/data GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/000077500000000000000000000000001460766671100264775ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/backends/000077500000000000000000000000001460766671100302515ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/backends/meson.build000066400000000000000000000022221460766671100324110ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later backend_tests_execdir = join_paths(installed_tests_execdir, 'backends') backend_tests_metadir = join_paths(installed_tests_metadir, 'backends') # Backend Suites backend_tests = [ 'LanBackend', ] foreach test : backend_tests test_file = files('test@0@.js'.format(test)) test(test, minijasmine, args: [test_file], depends: [installed_tests_prepared], env: test_env, protocol: 'tap', suite: 'backends' ) if get_option('installed_tests') test_config = configuration_data() test_config.set('jasmine_path', minijasmine_path) test_config.set('name', 'test@0@.js'.format(test)) test_config.set('installed_tests_execdir', backend_tests_execdir) test_description = configure_file( configuration: test_config, input: join_paths(installed_tests_srcdir, 'jasmine.test.in'), output: 'test@0@.test'.format(test), install_dir: backend_tests_metadir, ) install_data(test_file, install_dir: backend_tests_execdir, ) endif endforeach GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/backends/testLanBackend.js000066400000000000000000000111731460766671100334740ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import * as Utils from '../fixtures/utils.js'; import Config from '../config.js'; const Core = await import(`file://${Config.PACKAGE_DATADIR}/service/core.js`); const Lan = await import(`file://${Config.PACKAGE_DATADIR}/service/backends/lan.js`); describe('A LAN channel service', function () { let local, remote; let localChannel, remoteChannel; beforeAll(function () { const localCert = Gio.TlsCertificate.new_from_files( Utils.getDataPath('local-certificate.pem'), Utils.getDataPath('local-private.pem') ); local = new Lan.ChannelService({ id: GLib.uuid_string_random(), certificate: localCert, port: 1717, }); const remoteCert = Gio.TlsCertificate.new_from_files( Utils.getDataPath('remote-certificate.pem'), Utils.getDataPath('remote-private.pem') ); remote = new Lan.ChannelService({ id: GLib.uuid_string_random(), certificate: remoteCert, port: 1718, }); }); afterAll(function () { local.destroy(); remote.destroy(); }); it('can be started', function () { local.start(); expect(local.active).toBeTrue(); remote.start(); expect(remote.active).toBeTrue(); }); it('can request and accept channels', function (done) { const localId = local.connect('channel', (service, channel) => { local.disconnect(localId); localChannel = channel; if (localChannel && remoteChannel) done(); return true; }); const remoteId = remote.connect('channel', (service, channel) => { remote.disconnect(remoteId); remoteChannel = channel; if (localChannel && remoteChannel) done(); return true; }); local.broadcast('127.0.0.1:1718'); }); it('tracks active channels', function () { // NOTE: the broadcasting side uses it's own port for reconnect localChannel = local.channels.get(`lan://127.0.0.1:${local.port}`); expect(localChannel).toBeDefined(); remoteChannel = remote.channels.get(`lan://127.0.0.1:${local.port}`); expect(remoteChannel).toBeDefined(); }); describe('produces channels', function () { it('that can transfer packets', async function () { const outgoingPacket = new Core.Packet({ type: 'kdeconnect.test', body: { foo: GLib.uuid_string_random(), }, }); await localChannel.sendPacket(outgoingPacket); const incomingPacket = await remoteChannel.readPacket(); expect(incomingPacket.type).toBe(outgoingPacket.type); expect(incomingPacket.body.foo).toBe(outgoingPacket.body.foo); }); it('that can transfer payloads', async function () { // Uploading Channel const outgoingPacket = new Core.Packet({ type: 'kdeconnect.test', body: {foo: 'bar'}, }); const sentBytes = new GLib.Bytes(GLib.uuid_string_random()); const inputStream = Gio.MemoryInputStream.new_from_bytes(sentBytes); const localTransfer = new Core.Transfer({channel: localChannel}); localTransfer.addStream(outgoingPacket, inputStream, sentBytes.get_size()); localTransfer.start().catch(e => logError(e)); // Downloading Channel const incomingPacket = await remoteChannel.readPacket(); const outputStream = Gio.MemoryOutputStream.new_resizable(); const remoteTransfer = new Core.Transfer({channel: remoteChannel}); remoteTransfer.addStream(incomingPacket, outputStream); await remoteTransfer.start(); const receivedBytes = outputStream.steal_as_bytes(); expect(receivedBytes.equal(sentBytes)).toBeTrue(); }); }); it('can be stopped', function () { local.stop(); expect(local.active).toBeFalse(); remote.stop(); expect(remote.active).toBeFalse(); }); it('closes active channels when stopped', function () { expect(local.channels).toHaveSize(0); localChannel = null; expect(remote.channels).toHaveSize(0); remoteChannel = null; }); // TODO: restarting stopped services }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/components/000077500000000000000000000000001460766671100306645ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/components/meson.build000066400000000000000000000025601460766671100330310ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later component_tests_execdir = join_paths(installed_tests_execdir, 'components') component_tests_metadir = join_paths(installed_tests_metadir, 'components') # Component Suites component_tests = [ 'ClipboardComponent', # 'ContactsComponent', # 'InputComponent', 'MprisComponent', # 'NotificationComponent', # 'PulseaudioComponent', # 'SessionComponent', # 'SoundComponent', # 'UpowerComponent', ] foreach test : component_tests test_file = files('test@0@.js'.format(test)) test(test, minijasmine, args: [test_file], depends: [installed_tests_prepared], env: test_env, protocol: 'tap', suite: 'components' ) if get_option('installed_tests') test_config = configuration_data() test_config.set('jasmine_path', minijasmine_path) test_config.set('name', 'test@0@.js'.format(test)) test_config.set('installed_tests_execdir', component_tests_execdir) test_description = configure_file( configuration: test_config, input: join_paths(installed_tests_srcdir, 'jasmine.test.in'), output: 'test@0@.test'.format(test), install_dir: component_tests_metadir, ) install_data(test_file, install_dir: component_tests_execdir, ) endif endforeach testClipboardComponent.js000066400000000000000000000027221460766671100356300ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/components// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import '../fixtures/utils.js'; import Gdk from 'gi://Gdk'; import Gtk from 'gi://Gtk'; import GLib from 'gi://GLib'; import Config from '../config.js'; const {default: Clipboard} = await import(`file://${Config.PACKAGE_DATADIR}/service/components/clipboard.js`); describe('The Clipboard component', function () { let clipboard; let gtkClipboard; beforeAll(function () { Gtk.init(null); const display = Gdk.Display.get_default(); gtkClipboard = Gtk.Clipboard.get_default(display); clipboard = new Clipboard(); }); afterAll(function () { clipboard.destroy(); }); it('pulls changes from the session clipboard', function (done) { const text = GLib.uuid_string_random(); const id = clipboard.connect('notify::text', (clipboard) => { clipboard.disconnect(id); expect(clipboard.text).toBe(text); done(); }); gtkClipboard.set_text(text, -1); }); it('pushes changes to the session clipboard', function (done) { const text = GLib.uuid_string_random(); const id = gtkClipboard.connect('owner-change', (gtkClipboard) => { gtkClipboard.disconnect(id); expect(gtkClipboard.wait_for_text()).toBe(text); done(); }); clipboard.text = text; }); }); testMprisComponent.js000066400000000000000000000075171460766671100350320ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/components// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import '../fixtures/utils.js'; import MockPlayer from '../fixtures/mpris.js'; import Config from '../config.js'; const {default: Manager} = await import(`file://${Config.PACKAGE_DATADIR}/service/components/mpris.js`); // Prevent auto-loading Manager.prototype._loadPlayers = function () {}; describe('The MPRIS component', function () { let manager; let player; beforeAll(function () { manager = new Manager(); player = new MockPlayer(GLib.uuid_string_random()); }); afterAll(function () { manager.destroy(); }); describe('emits a signal', function () { it('when players appear on the bus', function (done) { const id = manager.connect('player-added', (manager, proxy) => { manager.disconnect(id); expect(proxy.Identity).toBe(player.Identity); done(); }); player.export(); }); it('when players are changed', function (done) { const id = manager.connect('player-changed', (manager, proxy) => { manager.disconnect(id); expect(proxy.Volume).toBe(0.5); done(); }); player.Volume = 0.5; }); it('when players are seeked', function (done) { const id = manager.connect('player-seeked', (manager, proxy, offset) => { manager.disconnect(id); expect(offset).toBe(1000); done(); }); player.emit('Seeked', 1000); }); it('when players vanish from the bus', function (done) { const id = manager.connect('player-removed', (manager, proxy) => { manager.disconnect(id); expect(proxy.Identity).toBe(player.Identity); done(); }); player.unexport(); }); }); describe('can track players', function () { beforeAll(function (done) { const id = manager.connect('player-added', (manager, proxy) => { manager.disconnect(id); done(); }); // Prep for pause/unpause tests player._CanPause = true; player._CanPlay = true; player._PlaybackStatus = 'Playing'; player.export(); }); afterAll(function (done) { const id = manager.connect('player-removed', (manager, proxy) => { manager.disconnect(id); done(); }); player.unexport(); }); it('and check for them', function () { expect(manager.hasPlayer(player.Identity)).toBeTrue(); }); it('and retrieve them', function () { const proxy = manager.getPlayer(player.Identity); expect(proxy.Identity).toBe(player.Identity); }); it('and list their identities', function () { expect(manager.getIdentities()).toContain(player.Identity); }); it('and pause them as a group', function (done) { const id = player.connect('notify::PlaybackStatus', (player) => { player.disconnect(id); expect(player.PlaybackStatus).toBe('Paused'); done(); }); manager.pauseAll(); }); it('and unpause them as a group', function (done) { pending('fix property propagation'); const id = player.connect('notify::PlaybackStatus', (player) => { player.disconnect(id); expect(player.PlaybackStatus).toBe('Playing'); done(); }); manager.unpauseAll(); }); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/core/000077500000000000000000000000001460766671100274275ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/core/meson.build000066400000000000000000000022171460766671100315730ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later core_tests_execdir = join_paths(installed_tests_execdir, 'core') core_tests_metadir = join_paths(installed_tests_metadir, 'core') # Core Suites core_tests = [ 'Device', 'Manager', 'Packet', 'Plugin', ] foreach test : core_tests test_file = files('test@0@.js'.format(test)) test(test, minijasmine, args: [test_file], depends: [installed_tests_prepared], env: test_env, protocol: 'tap', suite: 'core' ) if get_option('installed_tests') test_config = configuration_data() test_config.set('jasmine_path', minijasmine_path) test_config.set('name', 'test@0@.js'.format(test)) test_config.set('installed_tests_execdir', core_tests_execdir) test_description = configure_file( configuration: test_config, input: join_paths(installed_tests_srcdir, 'jasmine.test.in'), output: 'test@0@.test'.format(test), install_dir: core_tests_metadir, ) install_data(test_file, install_dir: core_tests_execdir, ) endif endforeach GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/core/testDevice.js000066400000000000000000000063631460766671100320740ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import * as Utils from '../fixtures/utils.js'; import Config from '../config.js'; const {default: Device} = await import(`file://${Config.PACKAGE_DATADIR}/service/device.js`); describe('A device constructed from a packet', function () { let device, identity; beforeAll(function () { identity = Utils.generateIdentity({ body: { incomingCapabilities: ['kdeconnect.ping'], outgoingCapabilities: ['kdeconnect.ping'], }, }); device = new Device(identity); }); afterAll(function () { device.destroy(); }); it('initializes properties', function () { expect(device.id).toBe(identity.body.deviceId); expect(device.name).toBe(identity.body.deviceName); expect(device.type).toBe(identity.body.deviceType); // expect(device.contacts).toBeTruthy(); expect(device.encryption_info).toBeTruthy(); expect(device.icon_name).toBeTruthy(); expect(device.connected).toBeFalse(); expect(device.paired).toBeFalse(); expect(device.settings).toBeInstanceOf(Gio.Settings); expect(device.menu).toBeInstanceOf(Gio.Menu); }); it('will not load plugins when unpaired', async function () { await device._loadPlugins(); expect(device._plugins).toHaveSize(0); }); it('will load plugins when paired', async function () { device._setPaired(true); expect(device.paired).toBeTrue(); await device._loadPlugins(); expect(device._plugins).toHaveSize(1); }); it('unloads plugins when unpaired', function () { device.unpair(); expect(device.paired).toBeFalse(); expect(device._plugins).toHaveSize(0); }); }); describe('A device constructed from an ID', function () { let device, id; beforeAll(function () { id = GLib.uuid_string_random(); device = new Device({body: {deviceId: id}}); }); afterAll(function () { device.destroy(); }); it('initializes properties', function () { expect(device.id).toBe(id); expect(device.name).toBe(''); expect(device.type).toBe('smartphone'); // expect(device.contacts).toBeTruthy(); expect(device.encryption_info).toBeTruthy(); expect(device.icon_name).toBeTruthy(); expect(device.connected).toBeFalse(); expect(device.paired).toBeFalse(); expect(device.settings).toBeInstanceOf(Gio.Settings); expect(device.menu).toBeInstanceOf(Gio.Menu); }); it('will not load plugins when unpaired', function () { device._loadPlugins(); expect(device._plugins).toHaveSize(0); }); it('will load plugins when paired', function () { device._setPaired(true); expect(device.paired).toBeTrue(); device._loadPlugins(); expect(device._plugins).toHaveSize(0); }); it('will unload plugins when unpaired', function () { device.unpair(); expect(device.paired).toBeFalse(); expect(device._plugins).toHaveSize(0); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/core/testManager.js000066400000000000000000000050661460766671100322460ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import * as Utils from '../fixtures/utils.js'; import Config from '../config.js'; const {default: Manager} = await import(`file://${Config.PACKAGE_DATADIR}/service/manager.js`); // TODO: * device management // * DBus describe('Manager', function () { let manager; let testRig; beforeAll(function () { manager = new Manager({ object_path: '/org/gnome/Shell/Extensions/GSConnect/Test', }); testRig = new Utils.TestRig(); spyOn(manager, '_loadDevices').and.callThrough(); spyOn(manager, '_loadBackends'); }); afterAll(function () { manager.destroy(); testRig.destroy(); }); it('sets defaults for required properties', function () { expect(manager.id).toBeTruthy(); expect(manager.name).toBeTruthy(); }); it('can be started', function () { manager.start(); // Disable auto-reconnect GLib.Source.remove(manager._reconnectId); manager._reconnectId = 0; expect(manager.active).toBeTrue(); expect(manager._loadDevices).toHaveBeenCalled(); expect(manager._loadBackends).toHaveBeenCalled(); }); it('can create devices for channels', function (done) { const {localService, remoteService} = testRig; // Managed service manager.backends.set('mock', localService); localService.__channelId = localService.connect('channel', manager._onChannel.bind(manager)); localService.start(); // Unmanaged service const id1 = remoteService.connect('channel', (service, channel) => { service.disconnect(id1); testRig.remoteChannel = channel; return true; }); remoteService.start(); // const id2 = manager.settings.connect('changed::devices', (settings) => { settings.disconnect(id2); expect(manager.devices).toHaveSize(1); done(); }); manager.identify(`mock://127.0.0.1:${remoteService.port}`); }); it('can be stopped', function () { manager.stop(); expect(manager.active).toBeFalse(); expect(manager.devices).toHaveSize(0); expect(manager.backends).toHaveSize(0); }); it('loads cached devices when started', function () { manager.start(); expect(manager.devices).toHaveSize(1); manager.stop(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/core/testPacket.js000066400000000000000000000063621460766671100321030ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import '../fixtures/utils.js'; import Config from '../config.js'; const Core = await import(`file://${Config.PACKAGE_DATADIR}/service/core.js`); /* * Test Packets */ const ObjectPacket = { id: Date.now(), type: 'kdeconnect.foo', body: { bar: 'baz', }, }; const PayloadPacket = { id: Date.now(), type: 'kdeconnect.foo', body: { bar: 'baz', }, payloadSize: Math.random() * 100, payloadTransferInfo: {port: 1739}, }; const DataPacket = `{ "id": 1234, "type": "kdeconnect.foo", "body": { "bar": "baz" } }`; describe('A packet', function () { let dataData, dataPacket; let objectData, objectPacket; let payloadData, payloadPacket; it('can be deserialized from an object', function () { objectPacket = new Core.Packet(ObjectPacket); expect(objectPacket.id).toBe(ObjectPacket.id); expect(objectPacket.type).toBe(ObjectPacket.type); expect(objectPacket.body.bar).toBe(ObjectPacket.body.bar); payloadPacket = new Core.Packet(PayloadPacket); expect(payloadPacket.id).toBe(PayloadPacket.id); expect(payloadPacket.type).toBe(PayloadPacket.type); expect(payloadPacket.body.bar).toBe(PayloadPacket.body.bar); }); it('can be deserialized from a data stream', function () { dataPacket = new Core.Packet(DataPacket); expect(dataPacket.id).toBe(1234); expect(dataPacket.type).toBe('kdeconnect.foo'); expect(dataPacket.body.bar).toBe('baz'); }); it('can be serialized to a data stream', function () { dataData = dataPacket.serialize(); expect(dataData[dataData.length - 1]).toBe('\n'); objectData = objectPacket.serialize(); expect(objectData[objectData.length - 1]).toBe('\n'); payloadData = payloadPacket.serialize(); expect(payloadData[payloadData.length - 1]).toBe('\n'); }); it('that has been serialized can be deserialized', function () { dataPacket = Core.Packet.deserialize(dataData); expect(dataPacket.id).not.toBe(1234); expect(dataPacket.type).toBe('kdeconnect.foo'); expect(dataPacket.body.bar).toBe('baz'); objectPacket = Core.Packet.deserialize(objectData); expect(objectPacket.id).not.toBe(ObjectPacket.id); expect(objectPacket.type).toBe(ObjectPacket.type); expect(objectPacket.body.bar).toBe(ObjectPacket.body.bar); payloadPacket = Core.Packet.deserialize(payloadData); expect(payloadPacket.id).not.toBe(PayloadPacket.id); expect(payloadPacket.type).toBe(PayloadPacket.type); expect(payloadPacket.body.bar).toBe(PayloadPacket.body.bar); }); it('can be converted to a useful string representation', function () { expect(dataPacket.toString()).toBe('[object Packet:kdeconnect.foo]'); expect(objectPacket.toString()).toBe('[object Packet:kdeconnect.foo]'); }); it('can check for a payload', function () { expect(dataPacket.hasPayload()).toBeFalse(); expect(objectPacket.hasPayload()).toBeFalse(); expect(payloadPacket.hasPayload()).toBeTrue(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/core/testPlugin.js000066400000000000000000000165651460766671100321400ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import * as Utils from '../fixtures/utils.js'; import Config from '../config.js'; const {default: Device} = await import(`file://${Config.PACKAGE_DATADIR}/service/device.js`); const Components = await import(`file://${Config.PACKAGE_DATADIR}/service/components/index.js`); const {default: Plugin} = await import(`file://${Config.PACKAGE_DATADIR}/service/plugin.js`); /* * Phony plugin */ var Metadata = { label: 'FooBarBaz', id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.FooBarBaz', incomingCapabilities: ['kdeconnect.foo'], outgoingCapabilities: ['kdeconnect.bar', 'kdeconnect.qux'], actions: { foo: { label: 'Foo', icon_name: 'dialog-information-symbolic', parameter_type: new GLib.VariantType('a{sv}'), incoming: ['kdeconnect.foo'], outgoing: [], }, bar: { label: 'Bar', icon_name: 'dialog-information-symbolic', parameter_type: new GLib.VariantType('b'), incoming: [], outgoing: ['kdeconnect.bar'], }, baz: { label: 'Baz', icon_name: 'dialog-information-symbolic', parameter_type: null, incoming: ['kdeconnect.foo'], outgoing: ['kdeconnect.baz'], }, }, }; const TestPlugin = GObject.registerClass({ GTypeName: 'GSConnectTestPlugin', }, class TestPlugin extends Plugin { _init(device) { super._init(device, 'foobarbaz', Metadata); this._cacheLoaded = false; this.data = 'default'; this._foo = null; } /* * Class methods */ cacheLoaded() { this._cacheLoaded = true; } cacheClear() { } connected() { super.connected(); } disconnected() { super.disconnected(); } handlePacket(packet) { } /* * Instance methods */ foo(params) { this._foo = params; } bar(bool) { this.device.sendPacket({ type: 'kdeconnect.bar', body: { bar: bool, }, }); } baz() { } destroy() { if (this._someComponent !== undefined) this._someComponent = Components.release('notification'); super.destroy(); } }); describe('Plugin GActions', function () { let device, plugin; beforeAll(function () { const identity = Utils.generateIdentity({ body: { incomingCapabilities: Metadata.outgoingCapabilities, outgoingCapabilities: Metadata.incomingCapabilities, }, }); device = new Device(identity); plugin = new TestPlugin(device); spyOn(plugin, 'foo').and.callThrough(); }); afterAll(function () { device.destroy(); }); it('are registered when constructed', function () { expect(device.has_action('foo')).toBeTrue(); expect(device.has_action('bar')).toBeTrue(); expect(device.has_action('baz')).toBeTrue(); }); it('are registered with the correct parameter type', function () { const fooParam = device.lookup_action('foo').get_parameter_type(); expect(fooParam.equal(Metadata.actions.foo.parameter_type)).toBeTrue(); const barParam = device.lookup_action('bar').get_parameter_type(); expect(barParam.equal(Metadata.actions.bar.parameter_type)).toBeTrue(); const bazParam = device.lookup_action('baz').get_parameter_type(); expect(bazParam).toBe(Metadata.actions.baz.parameter_type); }); it('are enabled when connected', function () { plugin.connected(); expect(plugin._gactions.some(action => action.enabled)).toBeTrue(); }); it('are only enabled if supported by the device', function () { expect(device.get_action_enabled('foo')).toBeTrue(); expect(device.get_action_enabled('bar')).toBeTrue(); expect(device.get_action_enabled('baz')).toBeFalse(); }); it('can be activated with complex parameters', function () { const parameter = new GLib.Variant('a{sv}', { 'string': GLib.Variant.new_string('qux'), 'icon': Gio.Icon.new_for_string('dialog-error-symbolic').serialize(), 'nested': new GLib.Variant('a{sb}', {'key': true}), }); device.lookup_action('foo').activate(parameter); expect(plugin.foo).toHaveBeenCalled(); expect(plugin._foo.string).toBe('qux'); expect(plugin._foo.icon instanceof Gio.Icon).toBeTrue(); expect(plugin._foo.nested.key).toBeTrue(); }); it('are disabled when disconnected', function () { plugin.disconnected(); expect(plugin._gactions.some(action => action.enabled)).toBeFalse(); }); it('are unregistered when destroyed', function () { plugin.destroy(); expect(device.has_action('foo')).toBeFalse(); expect(device.has_action('bar')).toBeFalse(); expect(device.has_action('baz')).toBeFalse(); }); }); describe('Plugin packets', function () { let device, plugin; beforeAll(function () { const identity = Utils.generateIdentity({ body: { incomingCapabilities: Metadata.outgoingCapabilities, outgoingCapabilities: Metadata.incomingCapabilities, }, }); device = new Device(identity); plugin = new TestPlugin(device); device._plugins.set('foobarbaz', plugin); device._handlers.set('kdeconnect.foo', plugin); device._setPaired(true); plugin.connected(); spyOn(device, 'sendPacket'); spyOn(plugin, 'handlePacket'); spyOn(plugin, 'bar').and.callThrough(); }); afterAll(function () { device._plugins.delete('foobarbaz'); device._handlers.delete('kdeconnect.foo'); plugin.destroy(); device.destroy(); }); it('can be sent by function', function () { plugin.bar(true); expect(device.sendPacket).toHaveBeenCalled(); }); it('can be sent by GAction', function () { device.lookup_action('bar').activate(new GLib.Variant('b', false)); expect(plugin.bar).toHaveBeenCalledWith(false); expect(device.sendPacket).toHaveBeenCalled(); }); it('can be received if supported', function () { const packet = { type: 'kdeconnect.foo', body: {}, }; device.handlePacket(packet); expect(plugin.handlePacket).toHaveBeenCalledWith(packet); }); }); // TODO describe('Plugin cache', function () { let device, plugin; beforeAll(function () { const identity = Utils.generateIdentity({ body: { incomingCapabilities: Metadata.outgoingCapabilities, outgoingCapabilities: Metadata.incomingCapabilities, }, }); device = new Device(identity); plugin = new TestPlugin(device); }); afterAll(function () { plugin.destroy(); device.destroy(); }); it('can be loaded', async function () { await expectAsync(plugin.cacheProperties(['data'])).toBeResolved(); expect(plugin._cacheLoaded).toBeTrue(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/meson.build000066400000000000000000000003051460766671100306370ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later subdir('core') subdir('backends') subdir('components') subdir('plugins') GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins/000077500000000000000000000000001460766671100301605ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins/meson.build000066400000000000000000000026371460766671100323320ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later plugin_tests_execdir = join_paths(installed_tests_execdir, 'plugins') plugin_tests_metadir = join_paths(installed_tests_metadir, 'plugins') # Plugin Suites plugin_tests = [ 'BatteryPlugin', 'ClipboardPlugin', 'ContactsPlugin', 'FindmyphonePlugin', 'MousepadPlugin', 'MprisPlugin', 'NotificationPlugin', 'PingPlugin', 'PresenterPlugin', 'RuncommandPlugin', 'SftpPlugin', 'SharePlugin', 'SmsPlugin', 'SystemvolumePlugin', 'TelephonyPlugin', ] foreach test : plugin_tests test_file = files('test@0@.js'.format(test)) test(test, minijasmine, args: [test_file], depends: [installed_tests_prepared], env: test_env, protocol: 'tap', suite: 'plugins' ) if get_option('installed_tests') test_config = configuration_data() test_config.set('jasmine_path', minijasmine_path) test_config.set('name', 'test@0@.js'.format(test)) test_config.set('installed_tests_execdir', plugin_tests_execdir) test_description = configure_file( configuration: test_config, input: join_paths(installed_tests_srcdir, 'jasmine.test.in'), output: 'test@0@.test'.format(test), install_dir: plugin_tests_metadir, ) install_data(test_file, install_dir: plugin_tests_execdir, ) endif endforeach testBatteryPlugin.js000066400000000000000000000155151460766671100341370ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; const Packets = { goodBattery: { type: 'kdeconnect.battery', body: { currentCharge: 50, isCharging: false, thresholdEvent: 0, }, }, lowBattery: { type: 'kdeconnect.battery', body: { currentCharge: 15, isCharging: false, thresholdEvent: 1, }, }, customBattery: { type: 'kdeconnect.battery', body: { currentCharge: 80, isCharging: true, thresholdEvent: 0, }, }, fullBattery: { type: 'kdeconnect.battery', body: { currentCharge: 100, isCharging: true, thresholdEvent: 0, }, }, }; describe('The battery plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { await Utils.mockComponents(); testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.battery', 'kdeconnect.battery.request', ], outgoingCapabilities: [ 'kdeconnect.battery', 'kdeconnect.battery.request', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.battery', 'kdeconnect.battery.request', ], outgoingCapabilities: [ 'kdeconnect.battery', 'kdeconnect.battery.request', ], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin && remotePlugin) { spyOn(remotePlugin, 'handlePacket').and.callThrough(); spyOn(remotePlugin, '_receiveState').and.callThrough(); spyOn(remotePlugin, '_requestState').and.callThrough(); spyOn(remotePlugin, '_sendState').and.callThrough(); spyOn(remotePlugin.device, 'showNotification'); spyOn(remotePlugin.device, 'hideNotification'); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('battery'); remotePlugin = testRig.remoteDevice._plugins.get('battery'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('sends and requests state updates when connected', async function () { testRig.setConnected(true); await remotePlugin.awaitPacket('kdeconnect.battery.request'); expect(remotePlugin._requestState).toHaveBeenCalled(); expect(remotePlugin._sendState).toHaveBeenCalled(); }); it('can receive state updates', async function () { localPlugin.device.sendPacket(Packets.goodBattery); await remotePlugin.awaitPacket('kdeconnect.battery', Packets.goodBattery.body); expect(remotePlugin._receiveState).toHaveBeenCalled(); }); it('updates properties', function () { expect(remotePlugin.charging).toBeFalse(); expect(remotePlugin.icon_name).toBe('battery-good-symbolic'); expect(remotePlugin.level).toBe(50); expect(remotePlugin.time).toBeGreaterThan(0); }); it('updates the GAction state', function () { const batteryAction = remotePlugin.device.lookup_action('battery'); const [charging, icon, level, time] = batteryAction.state.deepUnpack(); expect(charging).toBeFalse(); expect(icon).toBe('battery-good-symbolic'); expect(level).toBe(50); expect(time).toBeGreaterThan(0); }); it('notifies when the battery is low', async function () { localPlugin.device.sendPacket(Packets.lowBattery); await remotePlugin.awaitPacket('kdeconnect.battery', Packets.lowBattery.body); expect(remotePlugin.device.showNotification).toHaveBeenCalled(); }); it('withdraws low battery notifications', async function () { localPlugin.device.sendPacket(Packets.goodBattery); await remotePlugin.awaitPacket('kdeconnect.battery', Packets.goodBattery.body); expect(remotePlugin.device.hideNotification).toHaveBeenCalled(); }); it('notifies when the battery is at custom level', async function () { remotePlugin.settings.set_boolean('custom-battery-notification', true); localPlugin.device.sendPacket(Packets.customBattery); await remotePlugin.awaitPacket('kdeconnect.battery', Packets.customBattery.body); expect(remotePlugin.device.showNotification).toHaveBeenCalled(); }); it('withdraws custom battery notifications', async function () { localPlugin.device.sendPacket(Packets.goodBattery); await remotePlugin.awaitPacket('kdeconnect.battery', Packets.goodBattery.body); expect(remotePlugin.device.hideNotification).toHaveBeenCalled(); }); it('notifies when the battery is full', async function () { remotePlugin.settings.set_boolean('full-battery-notification', true); localPlugin.device.sendPacket(Packets.fullBattery, Packets.fullBattery.body); await remotePlugin.awaitPacket('kdeconnect.battery'); expect(remotePlugin.device.showNotification).toHaveBeenCalled(); }); it('withdraws full battery notifications', async function () { localPlugin.device.sendPacket(Packets.goodBattery); await remotePlugin.awaitPacket('kdeconnect.battery', Packets.goodBattery.body); expect(remotePlugin.device.hideNotification).toHaveBeenCalled(); }); describe('sends local statistics', function () { it('when enabled', async function () { localPlugin.settings.set_boolean('send-statistics', true); await remotePlugin.awaitPacket('kdeconnect.battery'); }); it('when they change', async function () { localPlugin._upower.update({ charging: true, level: 50, threshold: 0, }); await remotePlugin.awaitPacket('kdeconnect.battery', { currentCharge: 50, isCharging: true, thresholdEvent: 0, }); }); it('only if available', function () { spyOn(localPlugin.device, 'sendPacket'); localPlugin._upower.update({ is_present: false, }); expect(localPlugin.device.sendPacket).not.toHaveBeenCalled(); }); }); }); testClipboardPlugin.js000066400000000000000000000102751460766671100344220ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; describe('The clipboard plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { await Utils.mockComponents(); testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.clipboard', 'kdeconnect.clipboard.connect', ], outgoingCapabilities: [ 'kdeconnect.clipboard', 'kdeconnect.clipboard.connect', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.clipboard', 'kdeconnect.clipboard.connect', ], outgoingCapabilities: [ 'kdeconnect.clipboard', 'kdeconnect.clipboard.connect', ], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (remotePlugin && localPlugin) { spyOn(remotePlugin, 'handlePacket').and.callThrough(); spyOn(localPlugin.device, 'sendPacket').and.callThrough(); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('clipboard'); remotePlugin = testRig.remoteDevice._plugins.get('clipboard'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('enables its GActions when connected', function () { testRig.setConnected(true); expect(localPlugin.device.get_action_enabled('clipboardPush')).toBeTrue(); expect(localPlugin.device.get_action_enabled('clipboardPull')).toBeTrue(); expect(remotePlugin.device.get_action_enabled('clipboardPush')).toBeTrue(); expect(remotePlugin.device.get_action_enabled('clipboardPull')).toBeTrue(); }); it('sends initial clipboard content when connected', async function () { // Prime the clipboard and simulate a new connection localPlugin._localBuffer = localPlugin._clipboard.text; localPlugin._localTimestamp = Date.now(); localPlugin.settings.set_boolean('send-content', true); localPlugin.connected(); await remotePlugin.awaitPacket('kdeconnect.clipboard.connect'); expect(remotePlugin._remoteBuffer).toBe('initial'); localPlugin.settings.set_boolean('send-content', false); }); it('will not push content when not allowed', function () { localPlugin._clipboard.text = 'foo'; expect(localPlugin.device.sendPacket).not.toHaveBeenCalled(); }); it('will push content when allowed', async function () { localPlugin.settings.set_boolean('send-content', true); localPlugin._clipboard.text = 'bar'; await remotePlugin.awaitPacket('kdeconnect.clipboard'); expect(remotePlugin._remoteBuffer).toBe('bar'); }); it('will not pull content when not allowed', async function () { localPlugin._clipboard.text = 'baz'; await remotePlugin.awaitPacket('kdeconnect.clipboard'); expect(remotePlugin._clipboard.text).not.toBe('baz'); }); it('will pull content when allowed', async function () { remotePlugin.settings.set_boolean('receive-content', true); localPlugin._clipboard.text = 'qux'; await remotePlugin.awaitPacket('kdeconnect.clipboard'); expect(remotePlugin._clipboard.text).toBe('qux'); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); expect(localPlugin.device.get_action_enabled('clipboardPush')).toBeFalse(); expect(localPlugin.device.get_action_enabled('clipboardPull')).toBeFalse(); expect(remotePlugin.device.get_action_enabled('clipboardPush')).toBeFalse(); expect(remotePlugin.device.get_action_enabled('clipboardPull')).toBeFalse(); }); }); testContactsPlugin.js000066400000000000000000000106501460766671100342760ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; import GLib from 'gi://GLib'; import Config from '../config.js'; const VCards = { valid: Utils.loadDataContents('vcard-valid.vcf'), invalid: Utils.loadDataContents('vcard-invalid.vcf'), }; const Packets = { uidsResponse: { type: 'kdeconnect.contacts.response_uids_timestamps', body: { uids: ['valid', 'invalid'], valid: Date.now() + 10, invalid: Date.now() + 20, }, }, vcardsResponse: { type: 'kdeconnect.contacts.response_vcards', body: { uids: ['valid', 'invalid'], valid: VCards.valid, invalid: VCards.invalid, }, }, }; function handlePacket(packet) { if (packet.type === 'kdeconnect.contacts.request_all_uids_timestamps') { this.sendPacket(Packets.uidsResponse); } else if (packet.type === 'kdeconnect.contacts.request_vcards_by_uid') { const response = Packets.vcardsResponse; response.body = {uids: packet.body.uids}; for (const uid of response.body.uids) response.body[uid] = VCards[uid]; this.sendPacket(response); } } describe('The contacts plugin', function () { let testRig; let localPlugin; let remoteDevice; beforeAll(async function () { testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.contacts.request_all_uids_timestamps', 'kdeconnect.contacts.request_vcards_by_uid', ], outgoingCapabilities: [ 'kdeconnect.contacts.response_uids_timestamps', 'kdeconnect.contacts.response_vcards', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.contacts.response_uids_timestamps', 'kdeconnect.contacts.response_vcards', ], outgoingCapabilities: [ 'kdeconnect.contacts.request_all_uids_timestamps', 'kdeconnect.contacts.request_vcards_by_uid', ], }, }); testRig.setPaired(true); remoteDevice = testRig.remoteDevice; remoteDevice.handlePacket = handlePacket.bind(remoteDevice); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin) spyOn(localPlugin, 'handlePacket').and.callThrough(); }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('contacts'); expect(localPlugin).toBeDefined(); }); it('requests contacts when connected', async function () { GLib.test_expect_message('libebook-contacts', GLib.LogLevelFlags.LEVEL_WARNING, '*'); testRig.setConnected(true); await localPlugin.awaitPacket('kdeconnect.contacts.response_vcards'); expect(localPlugin._store.get_contact('valid')).toBeDefined(); }); it('clears the cache when requested', async function () { localPlugin.clearCache(); // TODO: this seems to indicate we're deferring too much while (localPlugin._store.contacts.length) await Promise.idle(); expect(localPlugin._store.contacts.length).toEqual(0); }); it('handles and stores vCards (EBookContacts)', async function () { localPlugin._requestVCards(['valid']); await localPlugin.awaitPacket('kdeconnect.contacts.response_vcards'); expect(localPlugin._store.get_contact('valid')).toBeDefined(); }); it('handles and stores vCards (native)', async function () { localPlugin.clearCache(); // TODO: this seems to indicate we're deferring too much while (localPlugin._store.contacts.length) await Promise.idle(); await import(`file://${Config.PACKAGE_DATADIR}/service/plugins/contacts.js`) .then(({setEBookContacts}) => setEBookContacts(null)); localPlugin._requestVCards(['valid']); await localPlugin.awaitPacket('kdeconnect.contacts.response_vcards'); expect(localPlugin._store.get_contact('valid')).toBeDefined(); }); }); testFindmyphonePlugin.js000066400000000000000000000046271460766671100350070ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; describe('The findmyphone plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { await Utils.mockComponents(); testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: ['kdeconnect.findmyphone.request'], outgoingCapabilities: ['kdeconnect.findmyphone.request'], }, remoteDevice: { incomingCapabilities: ['kdeconnect.findmyphone.request'], outgoingCapabilities: ['kdeconnect.findmyphone.request'], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin && remotePlugin) { spyOn(remotePlugin, 'handlePacket').and.callThrough(); spyOn(remotePlugin, '_handleRequest'); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('findmyphone'); remotePlugin = testRig.remoteDevice._plugins.get('findmyphone'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('enables its GActions when connected', function () { testRig.setConnected(true); expect(localPlugin.device.get_action_enabled('ring')).toBeTrue(); expect(remotePlugin.device.get_action_enabled('ring')).toBeTrue(); }); it('can send and receive ring requests', async function () { localPlugin.ring(); await remotePlugin.awaitPacket('kdeconnect.findmyphone.request'); expect(remotePlugin._handleRequest).toHaveBeenCalled(); }); it('stops ringing on the second request', async function () { localPlugin.ring(); await remotePlugin.awaitPacket('kdeconnect.findmyphone.request'); expect(remotePlugin._handleRequest).toHaveBeenCalled(); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); expect(localPlugin.device.get_action_enabled('ring')).toBeFalse(); expect(remotePlugin.device.get_action_enabled('ring')).toBeFalse(); }); }); testMousepadPlugin.js000066400000000000000000000237251460766671100343040ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; import Gdk from 'gi://Gdk'; describe('The mousepad plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { await Utils.mockComponents(); testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.mousepad.echo', 'kdeconnect.mousepad.keyboardstate', 'kdeconnect.mousepad.request', ], outgoingCapabilities: [ 'kdeconnect.mousepad.echo', 'kdeconnect.mousepad.keyboardstate', 'kdeconnect.mousepad.request', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.mousepad.echo', 'kdeconnect.mousepad.keyboardstate', 'kdeconnect.mousepad.request', ], outgoingCapabilities: [ 'kdeconnect.mousepad.echo', 'kdeconnect.mousepad.keyboardstate', 'kdeconnect.mousepad.request', ], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin && remotePlugin) { spyOn(localPlugin, 'handlePacket').and.callThrough(); spyOn(localPlugin, '_handleEcho').and.callThrough(); spyOn(remotePlugin, 'handlePacket').and.callThrough(); spyOn(remotePlugin, '_sendEcho').and.callThrough(); spyOn(remotePlugin._input, 'clickPointer'); spyOn(remotePlugin._input, 'doubleclickPointer'); spyOn(remotePlugin._input, 'pressPointer'); spyOn(remotePlugin._input, 'releasePointer'); spyOn(remotePlugin._input, 'movePointer'); spyOn(remotePlugin._input, 'scrollPointer'); spyOn(remotePlugin._input, 'pressKeys'); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('mousepad'); remotePlugin = testRig.remoteDevice._plugins.get('mousepad'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('enables its GActions when connected', function () { testRig.setConnected(true); expect(localPlugin.device.get_action_enabled('keyboard')).toBeTrue(); expect(remotePlugin.device.get_action_enabled('keyboard')).toBeTrue(); }); it('sends its keyboard state when connected', async function () { const localState = localPlugin.settings.get_boolean('share-control'); localPlugin.connected(); await remotePlugin.awaitPacket('kdeconnect.mousepad.keyboardstate'); expect(remotePlugin.state).toBe(localState); }); it('sends its keyboard state when changed', async function () { const previousState = localPlugin.settings.get_boolean('share-control'); localPlugin.settings.set_boolean('share-control', !previousState); await remotePlugin.awaitPacket('kdeconnect.mousepad.keyboardstate'); expect(remotePlugin.state).toBe(!previousState); }); describe('handles keypresses', function () { const keyModifiers = Gdk.ModifierType.MOD1_MASK | Gdk.ModifierType.SHIFT_MASK; const specialModifiers = Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SUPER_MASK; it('without modifiers', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: {key: 'a'}, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.pressKeys).toHaveBeenCalledWith('a', 0); }); it('with modifiers', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { key: 'b', alt: true, ctrl: false, shift: true, super: false, }, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.pressKeys).toHaveBeenCalledWith('b', keyModifiers); }); it('for special keys without modifiers', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: {specialKey: 1}, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.pressKeys).toHaveBeenCalledWith( Gdk.KEY_BackSpace, 0); }); it('for special keys with modifiers', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { specialKey: 2, alt: false, ctrl: true, shift: false, super: true, }, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.pressKeys).toHaveBeenCalledWith( Gdk.KEY_Tab, specialModifiers); }); it('and sends an acknowledging packet', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { key: 'c', sendAck: true, }, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._sendEcho).toHaveBeenCalled(); await localPlugin.awaitPacket('kdeconnect.mousepad.echo'); expect(localPlugin._handleEcho).toHaveBeenCalledWith({ key: 'c', isAck: true, }); }); }); describe('handles pointer events', function () { it('for movement', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { dx: 1, dy: -1, }, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.movePointer).toHaveBeenCalledWith(1, -1); }); it('for scrolling', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { dx: 0, dy: 1, scroll: true, }, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.scrollPointer).toHaveBeenCalledWith(0, 1); }); it('for left clicks', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: {singleclick: true}, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.clickPointer).toHaveBeenCalledWith( Gdk.BUTTON_PRIMARY); }); it('for middle clicks', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: {middleclick: true}, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.clickPointer).toHaveBeenCalledWith( Gdk.BUTTON_MIDDLE); }); it('for right clicks', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: {rightclick: true}, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.clickPointer).toHaveBeenCalledWith( Gdk.BUTTON_SECONDARY); }); it('for double clicks', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: {doubleclick: true}, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.doubleclickPointer).toHaveBeenCalledWith( Gdk.BUTTON_PRIMARY); }); it('for button presses', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: {singlehold: true}, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.pressPointer).toHaveBeenCalledWith( Gdk.BUTTON_PRIMARY); }); it('for button releases', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: {singlerelease: true}, }); await remotePlugin.awaitPacket('kdeconnect.mousepad.request'); expect(remotePlugin._input.releasePointer).toHaveBeenCalledWith( Gdk.BUTTON_PRIMARY); }); }); // TODO it('ignores input events when not allowed', function () { expect(true).toBeTrue(); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); expect(localPlugin.device.get_action_enabled('keyboard')).toBeFalse(); expect(remotePlugin.device.get_action_enabled('keyboard')).toBeFalse(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins/testMprisPlugin.js000066400000000000000000000234601460766671100336740ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; describe('The mpris plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { await Utils.mockComponents(); testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.mpris', 'kdeconnect.mpris.request', ], outgoingCapabilities: [ 'kdeconnect.mpris', 'kdeconnect.mpris.request', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.mpris', 'kdeconnect.mpris.request', ], outgoingCapabilities: [ 'kdeconnect.mpris', 'kdeconnect.mpris.request', ], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin && remotePlugin) { spyOn(localPlugin, 'handlePacket').and.callThrough(); spyOn(remotePlugin, 'handlePacket').and.callThrough(); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('mpris'); remotePlugin = testRig.remoteDevice._plugins.get('mpris'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('requests and reports players when connected', async function () { await testRig.setConnected(true); await Promise.all([ localPlugin.awaitPacket('kdeconnect.mpris.request'), remotePlugin.awaitPacket('kdeconnect.mpris.request'), localPlugin.awaitPacket('kdeconnect.mpris'), remotePlugin.awaitPacket('kdeconnect.mpris'), ]); }); it('adds players', async function () { localPlugin._mpris.addPlayer('Music Player'); expect(localPlugin._mpris.hasPlayer('Music Player')).toBeTrue(); await remotePlugin.awaitPacket('kdeconnect.mpris', { playerList: ['Music Player'], }); await remotePlugin.awaitPacket('kdeconnect.mpris', { player: 'Music Player', canGoNext: false, canGoPrevious: false, canPause: false, canPlay: false, canSeek: false, isPlaying: false, length: 0, pos: 0, volume: 100, }); expect(remotePlugin._players.has('Music Player')).toBeTrue(); }); it('sends and handles player changes', async function () { const localPlayer = localPlugin._mpris.getPlayer('Music Player'); const remotePlayer = remotePlugin._players.get('Music Player'); spyOn(remotePlayer, 'export'); // Update while accounting for the position/length/volume conversion localPlayer.update({ CanGoNext: true, CanGoPrevious: true, CanPause: true, CanPlay: true, CanSeek: true, PlaybackStatus: 'Playing', Position: 50000000, Volume: 0.5, Metadata: { 'xesam:artist': ['Some Artist'], 'xesam:album': 'Some Album', 'xesam:title': 'Track 1', 'mpris:length': 100000000, }, }); await remotePlugin.awaitPacket('kdeconnect.mpris', { player: 'Music Player', canGoNext: true, canGoPrevious: true, canPause: true, canPlay: true, canSeek: true, isPlaying: true, length: 100000, pos: 50000, volume: 50, artist: 'Some Artist', album: 'Some Album', title: 'Track 1', nowPlaying: 'Some Artist - Track 1', }); expect(remotePlayer.PlaybackStatus).toBe('Playing'); expect(remotePlayer.export).toHaveBeenCalled(); }); it('sends and handles player seeking', async function () { const localPlayer = localPlugin._mpris.getPlayer('Music Player'); const remotePlayer = remotePlugin._players.get('Music Player'); // Update while accounting for the offset conversion localPlayer.Seek(100000); // NOTE: although we can handle full seeked signals, kdeconnect-android // does not, and expects a position update instead await remotePlugin.awaitPacket('kdeconnect.mpris', { player: 'Music Player', pos: 50100, // Seek: 100, }); expect(remotePlayer.Position).toBe(50100000); }); it('sends and handles action commands', async function () { const localPlayer = localPlugin._mpris.getPlayer('Music Player'); const remotePlayer = remotePlugin._players.get('Music Player'); // Pause remotePlayer.Pause(); await localPlugin.awaitPacket('kdeconnect.mpris.request', { player: 'Music Player', action: 'Pause', }); expect(localPlayer.PlaybackStatus).toBe('Paused'); // Play remotePlayer.Play(); await localPlugin.awaitPacket('kdeconnect.mpris.request', { player: 'Music Player', action: 'Play', }); expect(localPlayer.PlaybackStatus).toBe('Playing'); // Play/Pause remotePlayer.PlayPause(); await localPlugin.awaitPacket('kdeconnect.mpris.request', { player: 'Music Player', action: 'PlayPause', }); expect(localPlayer.PlaybackStatus).toBe('Paused'); // Next remotePlayer.Next(); await localPlugin.awaitPacket('kdeconnect.mpris.request', { player: 'Music Player', action: 'Next', }); expect(localPlayer.Metadata['xesam:title']).toBe('Track 2'); // Previous remotePlayer.Previous(); await localPlugin.awaitPacket('kdeconnect.mpris.request', { player: 'Music Player', action: 'Previous', }); expect(localPlayer.Metadata['xesam:title']).toBe('Track 1'); // Stop remotePlayer.Stop(); await localPlugin.awaitPacket('kdeconnect.mpris.request', { player: 'Music Player', action: 'Stop', }); expect(localPlayer.PlaybackStatus).toBe('Stopped'); }); it('sends and receives album art', async function () { pending('FIXME'); const localPlayer = localPlugin._mpris.getPlayer('Music Player'); const remotePlayer = remotePlugin._players.get('Music Player'); const localUrl = Utils.getDataUri('album.png'); localPlayer.update({ player: 'Music Player', Metadata: { 'xesam:artist': ['Some Artist'], 'xesam:album': 'Some Album', 'xesam:title': 'Track 1', 'mpris:length': 100000000, 'mpris:artUrl': localUrl, }, }); await remotePlugin.awaitPacket('kdeconnect.mpris', { player: 'Music Player', albumArtUrl: localUrl, }); await new Promise((resolve, reject) => { remotePlayer.connect('notify::Metadata', () => { resolve(); }); }); // Wait for the album art to transfer const remoteUrl = remotePlayer._getFile(localUrl).get_uri(); const playerUrl = remotePlayer.Metadata['mpris:artUrl'].unpack(); expect(playerUrl).toBe(remoteUrl); }); it('unexports players when they can not be controlled', async function () { const localPlayer = localPlugin._mpris.getPlayer('Music Player'); const remotePlayer = remotePlugin._players.get('Music Player'); spyOn(remotePlayer, 'unexport'); localPlayer.update({ CanGoNext: false, CanGoPrevious: false, CanPause: false, CanPlay: false, CanSeek: false, }); await remotePlugin.awaitPacket('kdeconnect.mpris', { player: 'Music Player', canGoNext: false, canGoPrevious: false, canPause: false, canPlay: false, canSeek: false, }); expect(remotePlayer.unexport).toHaveBeenCalled(); }); it('exports players when they can be controlled', async function () { const localPlayer = localPlugin._mpris.getPlayer('Music Player'); const remotePlayer = remotePlugin._players.get('Music Player'); spyOn(remotePlayer, 'export'); localPlayer.update({ CanGoNext: true, CanGoPrevious: true, CanPause: true, CanPlay: true, CanSeek: true, }); await remotePlugin.awaitPacket('kdeconnect.mpris', { player: 'Music Player', canGoNext: true, canGoPrevious: true, canPause: true, canPlay: true, canSeek: true, }); expect(remotePlayer.export).toHaveBeenCalled(); }); it('removes players', async function () { localPlugin._mpris.removePlayer('Music Player'); expect(localPlugin._mpris.hasPlayer('Music Player')).toBeFalse(); await remotePlugin.awaitPacket('kdeconnect.mpris', { playerList: [], }); expect(remotePlugin._players.has('Music Player')).toBeFalse(); }); it('disables its GActions when disconnected', async function () { await testRig.setConnected(false); expect(true).toBeTrue(); }); }); testNotificationPlugin.js000066400000000000000000000234661460766671100351570ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import * as Utils from '../fixtures/utils.js'; const Notifications = { withoutIcon: { appName: 'Application', id: 'test-notification', title: 'Notification Title', text: 'Notification Body', ticker: 'Notification Title - Notification Body', time: '1599103247103', isClearable: true, }, withIcon: { appName: 'Application', id: 'test-notification', title: 'Notification Title', text: 'Notification Body', ticker: 'Notification Title - Notification Body', time: '1599103247103', isClearable: true, icon: new Gio.FileIcon({ file: Gio.File.new_for_uri(Utils.getDataUri('album.png')), }), }, repliable: { appName: 'Application', id: 'test-notification', title: 'Notification Title', text: 'Notification Body', ticker: 'Notification Title - Notification Body', time: '1599103247103', isClearable: true, requestReplyId: GLib.uuid_string_random(), }, actionable: { appName: 'Application', id: 'test-notification', title: 'Notification Title', text: 'Notification Body', ticker: 'Notification Title - Notification Body', time: '1599103247103', isClearable: true, actions: ['One', 'Two', 'Three'], }, }; describe('The notification plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { await Utils.mockComponents(); testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.notification', 'kdeconnect.notification.action', 'kdeconnect.notification.reply', 'kdeconnect.notification.request', ], outgoingCapabilities: [ 'kdeconnect.notification', 'kdeconnect.notification.action', 'kdeconnect.notification.reply', 'kdeconnect.notification.request', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.notification', 'kdeconnect.notification.action', 'kdeconnect.notification.reply', 'kdeconnect.notification.request', ], outgoingCapabilities: [ 'kdeconnect.notification', 'kdeconnect.notification.action', 'kdeconnect.notification.reply', 'kdeconnect.notification.request', ], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin && remotePlugin) { spyOn(localPlugin, 'handlePacket').and.callThrough(); spyOn(localPlugin.device, 'hideNotification'); spyOn(localPlugin.device, 'showNotification'); spyOn(remotePlugin, 'handlePacket').and.callThrough(); spyOn(remotePlugin.device, 'hideNotification'); spyOn(remotePlugin.device, 'showNotification'); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('notification'); remotePlugin = testRig.remoteDevice._plugins.get('notification'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('enables its GActions when connected', function () { testRig.setConnected(true); for (const action in localPlugin._meta.actions) expect(localPlugin.device.get_action_enabled(action)).toBeTrue(); }); it('request notifications when connected', async function () { spyOn(remotePlugin, '_handleNotificationRequest'); localPlugin.connected(); await remotePlugin.awaitPacket('kdeconnect.notification.request', { request: true, }); expect(remotePlugin._handleNotificationRequest).toHaveBeenCalled(); }); describe('can send and receive notifications', function () { it('without icons', async function () { localPlugin._listener.fakeNotification(Notifications.withoutIcon); await remotePlugin.awaitPacket('kdeconnect.notification', Notifications.withoutIcon); expect(remotePlugin.device.showNotification).toHaveBeenCalled(); }); it('with icons', async function () { localPlugin._listener.fakeNotification(Notifications.withIcon); await remotePlugin.awaitPacket('kdeconnect.notification', Notifications.withoutIcon); // while (!remotePlugin.device.showNotification.calls.any()) // await Promise.idle(); // expect(remotePlugin.device.showNotification).toHaveBeenCalled(); }); }); describe('ignores notifications', function () { beforeEach(function () { spyOn(localPlugin.device, 'sendPacket').and.callThrough(); }); it('when sending is not allowed', function () { localPlugin.settings.set_boolean('send-notifications', false); localPlugin._listener.fakeNotification(Notifications.withoutIcon); expect(localPlugin.device.sendPacket).not.toHaveBeenCalled(); localPlugin.settings.set_boolean('send-notifications', true); }); it('when sending in an active session is not allowed', function () { localPlugin.settings.set_boolean('send-active', false); localPlugin._listener.fakeNotification(Notifications.withoutIcon); expect(localPlugin.device.sendPacket).not.toHaveBeenCalled(); localPlugin.settings.set_boolean('send-active', true); }); it('when sending for the application is not allowed', function () { const applications = localPlugin.settings.get_string('applications'); const disabled = JSON.parse(applications); disabled['Application'].enabled = false; localPlugin.settings.set_string('applications', JSON.stringify(disabled)); localPlugin._listener.fakeNotification(Notifications.withoutIcon); expect(localPlugin.device.sendPacket).not.toHaveBeenCalled(); localPlugin.settings.set_string('applications', applications); }); }); it('can handle repliable notifications', async function () { // Ensure the packet sends... localPlugin._listener.fakeNotification(Notifications.repliable); await remotePlugin.awaitPacket('kdeconnect.notification', Notifications.repliable); expect(remotePlugin.device.showNotification).toHaveBeenCalled(); // ...then check the notification was properly formed const invocation = remotePlugin.device.showNotification.calls.first(); const notif = invocation.args[0]; expect(notif.action.name).toBe('replyNotification'); }); it('can send replies for repliable notifications', function () { spyOn(localPlugin.device, 'sendPacket'); const uuid = GLib.uuid_string_random(); const message = 'message'; localPlugin.replyNotification(uuid, message, {}); expect(localPlugin.device.sendPacket).toHaveBeenCalled(); }); it('can handle notifications with actions', async function () { // Ensure the packet sends... localPlugin._listener.fakeNotification(Notifications.actionable); await remotePlugin.awaitPacket('kdeconnect.notification', Notifications.actionable); expect(remotePlugin.device.showNotification).toHaveBeenCalled(); // ...then check the notification was properly formed const invocation = remotePlugin.device.showNotification.calls.first(); const notif = invocation.args[0]; expect(notif.buttons[0].label).toBe('One'); expect(notif.buttons[1].label).toBe('Two'); expect(notif.buttons[2].label).toBe('Three'); }); it('can activate actions for notifications', function () { spyOn(localPlugin.device, 'sendPacket'); const id = GLib.uuid_string_random(); const action = 'Action'; localPlugin.activateNotification(id, action); expect(localPlugin.device.sendPacket).toHaveBeenCalled(); }); it('can withdraw local notifications', async function () { const id = GLib.uuid_string_random(); localPlugin.withdrawNotification(id); await remotePlugin.awaitPacket('kdeconnect.notification', { id: id, isCancel: true, }); expect(remotePlugin.device.hideNotification).toHaveBeenCalledWith(id); }); it('can close remote notifications', async function () { spyOn(remotePlugin, '_handleNotificationRequest'); const id = GLib.uuid_string_random(); localPlugin.closeNotification(id); await remotePlugin.awaitPacket('kdeconnect.notification.request', { cancel: id, }); expect(remotePlugin._handleNotificationRequest).toHaveBeenCalled(); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); for (const action in localPlugin._meta.actions) expect(localPlugin.device.get_action_enabled(action)).toBeFalse(); for (const action in remotePlugin._meta.actions) expect(remotePlugin.device.get_action_enabled(action)).toBeFalse(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins/testPingPlugin.js000066400000000000000000000041301460766671100334700ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; describe('The ping plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: ['kdeconnect.ping'], outgoingCapabilities: ['kdeconnect.ping'], }, remoteDevice: { incomingCapabilities: ['kdeconnect.ping'], outgoingCapabilities: ['kdeconnect.ping'], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (remotePlugin) { spyOn(remotePlugin, 'handlePacket').and.callThrough(); spyOn(remotePlugin.device, 'showNotification'); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('ping'); remotePlugin = testRig.remoteDevice._plugins.get('ping'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('enables its GActions when connected', function () { testRig.setConnected(true); expect(localPlugin.device.get_action_enabled('ping')).toBeTrue(); expect(remotePlugin.device.get_action_enabled('ping')).toBeTrue(); }); it('can send and receive pings', async function () { localPlugin.ping(); await remotePlugin.awaitPacket('kdeconnect.ping'); expect(remotePlugin.handlePacket).toHaveBeenCalled(); expect(testRig.remoteDevice.showNotification).toHaveBeenCalled(); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); expect(localPlugin.device.get_action_enabled('ping')).toBeFalse(); expect(remotePlugin.device.get_action_enabled('ping')).toBeFalse(); }); }); testPresenterPlugin.js000066400000000000000000000037241460766671100344730ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; describe('The presenter plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.presenter', ], outgoingCapabilities: [ 'kdeconnect.presenter', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.presenter', ], outgoingCapabilities: [ 'kdeconnect.presenter', ], }, }); testRig.setPaired(true); testRig.setConnected(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (remotePlugin) { spyOn(remotePlugin, 'handlePacket').and.callThrough(); spyOn(remotePlugin._input, 'movePointer'); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('presenter'); remotePlugin = testRig.remoteDevice._plugins.get('presenter'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('can receive presentation commands', async function () { localPlugin.device.sendPacket({ type: 'kdeconnect.presenter', body: { dx: 0.1, dy: 0.1, }, }); await remotePlugin.awaitPacket('kdeconnect.presenter', { dx: 0.1, dy: 0.1, }); expect(remotePlugin._input.movePointer).toHaveBeenCalledWith(100, 100); }); }); testRuncommandPlugin.js000066400000000000000000000071231460766671100346240ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import * as Utils from '../fixtures/utils.js'; describe('The runcommand plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.runcommand', 'kdeconnect.runcommand.request', ], outgoingCapabilities: [ 'kdeconnect.runcommand', 'kdeconnect.runcommand.request', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.runcommand', 'kdeconnect.runcommand.request', ], outgoingCapabilities: [ 'kdeconnect.runcommand', 'kdeconnect.runcommand.request', ], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin && remotePlugin) { spyOn(localPlugin, 'handlePacket').and.callThrough(); spyOn(remotePlugin, 'handlePacket').and.callThrough(); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('runcommand'); remotePlugin = testRig.remoteDevice._plugins.get('runcommand'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('enables its GActions when connected', function () { testRig.setConnected(true); expect(localPlugin.device.get_action_enabled('executeCommand')).toBeTrue(); expect(remotePlugin.device.get_action_enabled('executeCommand')).toBeTrue(); }); it('sends and request the list of commands when connected', async function () { localPlugin.connected(); await remotePlugin.awaitPacket('kdeconnect.runcommand.request', { requestCommandList: true, }); await remotePlugin.awaitPacket('kdeconnect.runcommand', { commandList: {}, }); }); it('sends the command list when it changes', async function () { const commandList = new GLib.Variant('a{sv}', { 'command-uuid': new GLib.Variant('a{ss}', { name: 'Test Command', command: 'ls', }), }); localPlugin.settings.set_value('command-list', commandList); await remotePlugin.awaitPacket('kdeconnect.runcommand', { commandList: '{"command-uuid":{"name":"Test Command","command":"ls"}}', }); expect(remotePlugin.remote_commands['command-uuid']).toBeDefined(); }); it('can activate a remote command', async function () { spyOn(localPlugin.device, 'launchProcess'); remotePlugin.executeCommand('command-uuid'); await localPlugin.awaitPacket('kdeconnect.runcommand.request', { key: 'command-uuid', }); expect(localPlugin.device.launchProcess).toHaveBeenCalled(); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); expect(localPlugin.device.get_action_enabled('executeCommand')).toBeFalse(); expect(remotePlugin.device.get_action_enabled('executeCommand')).toBeFalse(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins/testSftpPlugin.js000066400000000000000000000064531460766671100335210ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; import Config from '../config.js'; const {default: Plugin} = await import(`file://${Config.PACKAGE_DATADIR}/service/plugin.js`); const Packets = { response: { type: 'kdeconnect.sftp', body: { ip: '127.0.0.1', port: 2039, user: 'kdeconnect', password: 'remote-password', path: '/', multiPaths: [ '/remote-directory', ], pathNames: [ 'Remote', ], }, }, error: { type: 'kdeconnect.sftp', body: { errorMessage: 'Error Message', }, }, }; function handlePacket(packet) { switch (packet.type) { case 'kdeconnect.sftp.request': this.sendPacket(Packets.response); break; } } describe('The sftp plugin', function () { let testRig; let localPlugin; let remoteDevice; beforeAll(async function () { testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: ['kdeconnect.sftp.request'], outgoingCapabilities: ['kdeconnect.sftp'], }, remoteDevice: { incomingCapabilities: ['kdeconnect.sftp'], outgoingCapabilities: ['kdeconnect.sftp'], }, }); testRig.setPaired(true); remoteDevice = testRig.remoteDevice; remoteDevice.handlePacket = handlePacket.bind(remoteDevice); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin) { spyOn(localPlugin, 'handlePacket').and.callThrough(); spyOn(localPlugin, '_handleMount'); spyOn(localPlugin.device, 'showNotification'); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('sftp'); expect(localPlugin).toBeDefined(); }); it('enables its GActions when connected', function () { testRig.setConnected(true); // NOTE: chaining-up to avoid the guard against Core.Channel type Plugin.prototype.connected.call(localPlugin); expect(localPlugin.device.get_action_enabled('mount')).toBeTrue(); expect(localPlugin.device.get_action_enabled('unmount')).toBeTrue(); }); it('can request a mount', async function () { localPlugin.mount(); await localPlugin.awaitPacket('kdeconnect.sftp', Packets.response.body); expect(localPlugin._handleMount).toHaveBeenCalled(); }); it('can handle error messages', async function () { remoteDevice.sendPacket(Packets.error); await localPlugin.awaitPacket('kdeconnect.sftp', Packets.error.body); expect(localPlugin.device.showNotification).toHaveBeenCalled(); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); expect(localPlugin.device.get_action_enabled('mount')).toBeFalse(); expect(localPlugin.device.get_action_enabled('unmount')).toBeFalse(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins/testSharePlugin.js000066400000000000000000000070311460766671100336400ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; describe('The share plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.share.request', ], outgoingCapabilities: [ 'kdeconnect.share.request', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.share.request', ], outgoingCapabilities: [ 'kdeconnect.share.request', ], }, }); testRig.setPaired(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (remotePlugin) spyOn(remotePlugin, 'handlePacket').and.callThrough(); }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('share'); remotePlugin = testRig.remoteDevice._plugins.get('share'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); }); it('enables its GActions when connected', function () { testRig.setConnected(true); for (const action in localPlugin._meta.actions) expect(localPlugin.device.get_action_enabled(action)).toBeTrue(); for (const action in remotePlugin._meta.actions) expect(remotePlugin.device.get_action_enabled(action)).toBeTrue(); }); it('can send and receive files', async function () { spyOn(remotePlugin, '_handleFile'); localPlugin.shareFile(Utils.getDataPath('album.png')); await remotePlugin.awaitPacket('kdeconnect.share.request', { filename: 'album.png', }); expect(remotePlugin._handleFile).toHaveBeenCalled(); }); it('can send and receive text', async function () { spyOn(remotePlugin, '_handleText'); localPlugin.shareText('shared text'); await remotePlugin.awaitPacket('kdeconnect.share.request', { text: 'shared text', }); expect(remotePlugin._handleText).toHaveBeenCalled(); }); it('can send and receive URIs', async function () { spyOn(remotePlugin, '_handleUri'); localPlugin.shareUri('https://www.gnome.org/'); await remotePlugin.awaitPacket('kdeconnect.share.request', { url: 'https://www.gnome.org/', }); expect(remotePlugin._handleUri).toHaveBeenCalled(); }); xit('interprets file URIs as file shares', async function () { spyOn(remotePlugin, '_handleFile'); localPlugin.shareUri('file:///home/user/file.ext'); await remotePlugin.awaitPacket('kdeconnect.share.request', { filename: 'file.ext', }); expect(remotePlugin._handleFile).toHaveBeenCalled(); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); for (const action in localPlugin._meta.actions) expect(localPlugin.device.get_action_enabled(action)).toBeFalse(); for (const action in remotePlugin._meta.actions) expect(remotePlugin.device.get_action_enabled(action)).toBeFalse(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins/testSmsPlugin.js000066400000000000000000000206211460766671100333400ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; const Packets = { summary: { type: 'kdeconnect.sms.messages', body: { messages: [ { addresses: [ { address: '555-555-5555', }, ], body: 'incoming message of thread 1', date: 1588334621800, type: 1, read: 0, thread_id: 1, _id: 1, sub_id: 1, event: 1, }, { addresses: [ { address: '555-555-5556', }, ], body: 'incoming message of thread 2', date: 1588334621500, type: 1, read: 0, thread_id: 2, _id: 3, sub_id: 1, event: 1, }, ], version: 2, }, }, thread_one: { type: 'kdeconnect.sms.messages', body: { messages: [ { addresses: [ { address: '555-555-5555', }, ], body: 'incoming message of thread 1', date: 1588334621800, type: 1, read: 0, thread_id: 1, _id: 1, sub_id: 1, event: 1, }, { addresses: [ { address: '555-555-5555', }, ], body: 'outgoing message of thread 1', date: 1588334621700, type: 2, read: 0, thread_id: 1, _id: 2, sub_id: 1, event: 1, }, ], version: 2, }, }, thread_two: { type: 'kdeconnect.sms.messages', body: { messages: [ { addresses: [ { address: '555-555-5556', }, ], body: 'incoming message of thread 2', date: 1588334621500, type: 1, read: 0, thread_id: 2, _id: 3, sub_id: 1, event: 1, }, { addresses: [ { address: '555-555-5556', }, ], body: 'outgoing message of thread 2', date: 1588334621400, type: 2, read: 0, thread_id: 2, _id: 4, sub_id: 1, event: 1, }, ], version: 2, }, }, }; function handlePacket(packet) { switch (packet.type) { case 'kdeconnect.sms.request_conversations': this.sendPacket(Packets.summary); break; case 'kdeconnect.sms.request_conversation': if (packet.body.threadID === '1') this.sendPacket(Packets.thread_one); else if (packet.body.threadID === '2') this.sendPacket(Packets.thread_two); break; } } describe('The sms plugin', function () { let testRig; let localPlugin; let remoteDevice; beforeAll(async function () { testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.sms.messages', 'kdeconnect.sms.request', 'kdeconnect.sms.request_conversation', 'kdeconnect.sms.request_conversations', ], outgoingCapabilities: [ 'kdeconnect.sms.messages', 'kdeconnect.sms.request', 'kdeconnect.sms.request_conversation', 'kdeconnect.sms.request_conversations', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.sms.messages', 'kdeconnect.sms.request', 'kdeconnect.sms.request_conversation', 'kdeconnect.sms.request_conversations', ], outgoingCapabilities: [ 'kdeconnect.sms.messages', 'kdeconnect.sms.request', 'kdeconnect.sms.request_conversation', 'kdeconnect.sms.request_conversations', ], }, }); testRig.setPaired(true); remoteDevice = testRig.remoteDevice; remoteDevice.handlePacket = handlePacket.bind(remoteDevice); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin) spyOn(localPlugin, 'handlePacket').and.callThrough(); }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('sms'); expect(localPlugin).toBeDefined(); }); it('enables its GActions when connected', function () { spyOn(localPlugin, '_requestConversations'); testRig.setConnected(true); for (const action in localPlugin._meta.actions) expect(localPlugin.device.get_action_enabled(action)).toBeTrue(); }); it('requests messages when connected', function () { spyOn(localPlugin, '_requestConversations'); localPlugin.connected(); expect(localPlugin._requestConversations).toHaveBeenCalled(); }); it('can request a list of conversations', async function () { spyOn(localPlugin, '_handleDigest'); localPlugin._requestConversations(); await localPlugin.awaitPacket('kdeconnect.sms.messages'); expect(localPlugin._handleDigest).toHaveBeenCalled(); }); it('can request full conversations', async function () { spyOn(localPlugin, '_handleDigest').and.callThrough(); spyOn(localPlugin, '_handleThread').and.callThrough(); spyOn(localPlugin, '_requestConversation').and.callThrough(); localPlugin._requestConversations(); await localPlugin.awaitPacket('kdeconnect.sms.messages'); expect(localPlugin._handleDigest).toHaveBeenCalled(); expect(localPlugin._requestConversation).toHaveBeenCalledTimes(2); localPlugin.handlePacket.calls.reset(); await localPlugin.awaitPacket('kdeconnect.sms.messages'); expect(localPlugin._handleThread).toHaveBeenCalled(); }); it('only requests new or updated converations', async function () { spyOn(localPlugin, '_handleDigest').and.callThrough(); spyOn(localPlugin, '_handleThread').and.callThrough(); spyOn(localPlugin, '_requestConversation').and.callThrough(); localPlugin._requestConversations(); await localPlugin.awaitPacket('kdeconnect.sms.messages'); expect(localPlugin._handleDigest).toHaveBeenCalled(); expect(localPlugin._requestConversation).not.toHaveBeenCalled(); }); it('can send SMS messages', async function () { spyOn(remoteDevice, 'handlePacket').and.callThrough(); localPlugin.sendSms('555-555-5555', 'message body'); await remoteDevice.awaitPacket('kdeconnect.sms.request', { sendSms: true, phoneNumber: '555-555-5555', messageBody: 'message body', }); }); it('disables its GActions when disconnected', function () { testRig.setConnected(false); for (const action in localPlugin._meta.actions) expect(localPlugin.device.get_action_enabled(action)).toBeFalse(); }); }); testSystemvolumePlugin.js000066400000000000000000000067341460766671100352440ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; function handlePacket(packet) { switch (packet.type) { case 'kdeconnect.systemvolume': break; case 'kdeconnect.systemvolume.request': break; } } describe('The systemvolume plugin', function () { let testRig; let localPlugin; let remoteDevice; beforeAll(async function () { await Utils.mockComponents(); testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.systemvolume', 'kdeconnect.systemvolume.request', ], outgoingCapabilities: [ 'kdeconnect.systemvolume', 'kdeconnect.systemvolume.request', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.systemvolume', 'kdeconnect.systemvolume.request', ], outgoingCapabilities: [ 'kdeconnect.systemvolume', 'kdeconnect.systemvolume.request', ], }, }); testRig.setPaired(true); remoteDevice = testRig.remoteDevice; remoteDevice.handlePacket = handlePacket.bind(remoteDevice); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin) spyOn(localPlugin, 'handlePacket').and.callThrough(); }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('systemvolume'); expect(localPlugin).toBeDefined(); }); it('sends streams when connected', function () { spyOn(localPlugin, '_sendSinkList'); testRig.setConnected(true); expect(localPlugin._sendSinkList).toHaveBeenCalled(); }); it('sends a list of streams when requested', async function () { spyOn(remoteDevice, 'handlePacket').and.callThrough(); remoteDevice.sendPacket({ type: 'kdeconnect.systemvolume.request', body: { requestSinks: true, }, }); await localPlugin.awaitPacket('kdeconnect.systemvolume.request', { requestSinks: true, }); await remoteDevice.awaitPacket('kdeconnect.systemvolume'); }); it('handles volume level requests', async function () { remoteDevice.sendPacket({ type: 'kdeconnect.systemvolume.request', body: { name: '0', volume: 2, }, }); await localPlugin.awaitPacket('kdeconnect.systemvolume.request', { name: '0', volume: 2, }); expect(localPlugin._mixer.lookup_sink(0).volume).toBe(2); }); it('handles mute requests', async function () { remoteDevice.sendPacket({ type: 'kdeconnect.systemvolume.request', body: { name: '0', muted: true, }, }); await localPlugin.awaitPacket('kdeconnect.systemvolume.request', { name: '0', muted: true, }); expect(localPlugin._mixer.lookup_sink(0).muted).toBeTrue(); }); }); testTelephonyPlugin.js000066400000000000000000000240051460766671100344660ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/installed-tests/suites/plugins// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Utils from '../fixtures/utils.js'; const Packets = { ringing: { type: 'kdeconnect.telephony', body: { contactName: 'Name', phoneNumber: '555-555-5555', event: 'ringing', }, }, ringingCancel: { type: 'kdeconnect.telephony', body: { isCancel: true, contactName: 'Name', phoneNumber: '555-555-5555', event: 'ringing', }, }, talking: { type: 'kdeconnect.telephony', body: { contactName: 'Name', phoneNumber: '555-555-5555', event: 'talking', }, }, talkingCancel: { type: 'kdeconnect.telephony', body: { isCancel: true, contactName: 'Name', phoneNumber: '555-555-5555', event: 'ringing', }, }, }; describe('The telephony plugin', function () { let testRig; let localPlugin, remotePlugin; beforeAll(async function () { await Utils.mockComponents(); testRig = new Utils.TestRig(); await testRig.prepare({ localDevice: { incomingCapabilities: [ 'kdeconnect.telephony.request', 'kdeconnect.telephony.request_mute', ], outgoingCapabilities: [ 'kdeconnect.telephony', ], }, remoteDevice: { incomingCapabilities: [ 'kdeconnect.telephony.request', 'kdeconnect.telephony.request_mute', ], outgoingCapabilities: [ 'kdeconnect.telephony', ], }, }); testRig.setPaired(true); testRig.setConnected(true); }); afterAll(function () { testRig.destroy(); }); beforeEach(function () { if (localPlugin) { spyOn(localPlugin, 'handlePacket').and.callThrough(); spyOn(localPlugin.device, 'showNotification'); spyOn(localPlugin.device, 'hideNotification'); } }); it('can be loaded', async function () { await testRig.loadPlugins(); localPlugin = testRig.localDevice._plugins.get('telephony'); remotePlugin = testRig.remoteDevice._plugins.get('telephony'); expect(localPlugin).toBeDefined(); expect(remotePlugin).toBeDefined(); // Unset the event triggers for initial tests localPlugin.settings.set_string('ringing-volume', 'nothing'); localPlugin.settings.set_boolean('ringing-pause', false); localPlugin.settings.set_string('talking-volume', 'nothing'); localPlugin.settings.set_boolean('talking-microphone', false); localPlugin.settings.set_boolean('talking-pause', false); }); it('enables its GActions when connected', function () { testRig.setConnected(true); expect(localPlugin.device.get_action_enabled('muteCall')).toBeTrue(); }); it('shows a notification when the phone is ringing', async function () { remotePlugin.device.sendPacket(Packets.ringing); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.ringing.body); expect(localPlugin.device.showNotification).toHaveBeenCalled(); }); it('hides the notification if the phone stops ringing', async function () { remotePlugin.device.sendPacket(Packets.ringingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.ringingCancel.body); expect(localPlugin.device.hideNotification).toHaveBeenCalled(); }); it('shows a notification when the phone is answered', async function () { remotePlugin.device.sendPacket(Packets.talking); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talking.body); expect(localPlugin.device.showNotification).toHaveBeenCalled(); }); it('hides the notification when the call ends', async function () { remotePlugin.device.sendPacket(Packets.talkingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talkingCancel.body); expect(localPlugin.device.hideNotification).toHaveBeenCalled(); }); describe('can lower and restore the volume', function () { let localMixer; beforeEach(function () { localMixer = localPlugin._mixer; spyOn(localMixer, 'lowerVolume'); spyOn(localMixer, 'restore'); }); it('when the phone is ringing', async function () { localPlugin.settings.set_string('ringing-volume', 'lower'); remotePlugin.device.sendPacket(Packets.ringing); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.ringing.body); expect(localMixer.lowerVolume).toHaveBeenCalled(); remotePlugin.device.sendPacket(Packets.ringingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.ringingCancel.body); expect(localMixer.restore).toHaveBeenCalled(); }); it('when the phone is answered', async function () { localPlugin.settings.set_string('talking-volume', 'lower'); // Start remotePlugin.device.sendPacket(Packets.talking); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talking.body); expect(localMixer.lowerVolume).toHaveBeenCalled(); // End remotePlugin.device.sendPacket(Packets.talkingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talkingCancel.body); expect(localMixer.restore).toHaveBeenCalled(); }); }); describe('can mute and unmute the volume', function () { let localMixer; beforeEach(function () { localMixer = localPlugin._mixer; spyOn(localMixer, 'muteVolume'); spyOn(localMixer, 'restore'); }); it('when the phone is ringing', async function () { localPlugin.settings.set_string('ringing-volume', 'mute'); remotePlugin.device.sendPacket(Packets.ringing); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.ringing.body); expect(localMixer.muteVolume).toHaveBeenCalled(); remotePlugin.device.sendPacket(Packets.ringingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.ringingCancel.body); expect(localMixer.restore).toHaveBeenCalled(); }); it('when the phone is answered', async function () { localPlugin.settings.set_string('talking-volume', 'mute'); // Start remotePlugin.device.sendPacket(Packets.talking); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talking.body); expect(localMixer.muteVolume).toHaveBeenCalled(); // End remotePlugin.device.sendPacket(Packets.talkingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talkingCancel.body); expect(localMixer.restore).toHaveBeenCalled(); }); }); describe('can mute and unmute the microphone', function () { let localMixer; beforeEach(function () { localMixer = localPlugin._mixer; spyOn(localMixer, 'muteMicrophone'); spyOn(localMixer, 'restore'); }); it('when the phone is answered', async function () { localPlugin.settings.set_boolean('talking-microphone', true); // Start remotePlugin.device.sendPacket(Packets.talking); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talking.body); expect(localMixer.muteMicrophone).toHaveBeenCalled(); // End remotePlugin.device.sendPacket(Packets.talkingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talkingCancel.body); expect(localMixer.restore).toHaveBeenCalled(); }); }); describe('can pause and unpause media', function () { let localMedia; beforeEach(function () { localMedia = localPlugin._mpris; spyOn(localMedia, 'pauseAll'); spyOn(localMedia, 'unpauseAll'); }); it('when the phone is ringing', async function () { localPlugin.settings.set_boolean('ringing-pause', true); remotePlugin.device.sendPacket(Packets.ringing); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.ringing.body); expect(localMedia.pauseAll).toHaveBeenCalled(); remotePlugin.device.sendPacket(Packets.ringingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.ringingCancel.body); expect(localMedia.unpauseAll).toHaveBeenCalled(); }); it('when the phone is answered', async function () { localPlugin.settings.set_boolean('talking-pause', true); // Start remotePlugin.device.sendPacket(Packets.talking); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talking.body); expect(localMedia.pauseAll).toHaveBeenCalled(); // End remotePlugin.device.sendPacket(Packets.talkingCancel); await localPlugin.awaitPacket('kdeconnect.telephony', Packets.talkingCancel.body); expect(localMedia.unpauseAll).toHaveBeenCalled(); }); }); it('disabled its GActions when disconnected', function () { testRig.setConnected(false); expect(localPlugin.device.get_action_enabled('muteCall')).toBeFalse(); }); }); GSConnect-gnome-shell-extension-gsconnect-43258f9/meson.build000066400000000000000000000075641460766671100242220ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later project('gsconnect', 'c', version: '57', meson_version: '>= 0.59.0', ) gnome = import('gnome') i18n = import('i18n') app_id = 'org.gnome.Shell.Extensions.GSConnect' app_path = '/org/gnome/Shell/Extensions/GSConnect' extuuid = 'gsconnect@andyholmes.github.io' prefix = get_option('prefix') datadir = join_paths(prefix, get_option('datadir')) extdatadir = join_paths(datadir, 'gnome-shell', 'extensions', extuuid) libdir = join_paths(prefix, get_option('libdir')) libexecdir = join_paths(prefix, get_option('libexecdir')) localedir = join_paths(prefix, get_option('localedir')) sysconfdir = get_option('sysconfdir') gschemadir = join_paths(datadir, 'glib-2.0', 'schemas') # GNOME Shell LIBDIR if get_option('gnome_shell_libdir') != '' gnome_shell_libdir = get_option('gnome_shell_libdir') else gnome_shell_libdir = libdir endif # Configuration extconfig = configuration_data() extconfig.set('PACKAGE_VERSION', meson.project_version()) extconfig.set('PACKAGE_URL', 'https://github.com/GSConnect/gnome-shell-extension-gsconnect') extconfig.set('PACKAGE_BUGREPORT', 'https://github.com/GSConnect/gnome-shell-extension-gsconnect/issues/new') extconfig.set('PACKAGE_DATADIR', extdatadir) extconfig.set('PACKAGE_LOCALEDIR', localedir) extconfig.set('GSETTINGS_SCHEMA_DIR', gschemadir) extconfig.set('APPLICATION_ID', app_id) extconfig.set('APPLICATION_PATH', app_path) extconfig.set('GNOME_SHELL_LIBDIR', gnome_shell_libdir) extconfig.set('FFMPEG_PATH', get_option('ffmpeg_path')) extconfig.set('OPENSSL_PATH', get_option('openssl_path')) extconfig.set('SSHADD_PATH', get_option('sshadd_path')) extconfig.set('SSHKEYGEN_PATH', get_option('sshkeygen_path')) # ZIP targets for user extension builds env_util = find_program('env') run_target( 'make-pkgdir', command: [ env_util, 'UUID=' + extuuid, 'DATADIR=' + datadir, 'LOCALEDIR=' + localedir, 'GSCHEMADIR=' + gschemadir, 'NOZIP=true', join_paths(meson.project_source_root(), 'build-aux', 'ego', 'mkzip.sh') ] ) run_target( 'make-zip', command: [ env_util, 'UUID=' + extuuid, 'DATADIR=' + datadir, 'LOCALEDIR=' + localedir, 'GSCHEMADIR=' + gschemadir, join_paths(meson.project_source_root(), 'build-aux', 'ego', 'mkzip.sh') ] ) run_target( 'install-zip', command: [ env_util, 'UUID=' + extuuid, 'DATADIR=' + datadir, 'LOCALEDIR=' + localedir, 'GSCHEMADIR=' + gschemadir, 'INSTALL=true', join_paths(meson.project_source_root(), 'build-aux', 'ego', 'mkzip.sh') ] ) # Post-Install script for distributions without the hooks gnome.post_install( glib_compile_schemas: true, gtk_update_icon_cache: true, update_desktop_database: true, ) # Extension Source install_subdir( 'src', install_dir: extdatadir, strip_directory: true ) eslint = find_program('eslint', required: false) if eslint.found() test('ESLint (Source)', eslint, args: join_paths(meson.project_source_root(), 'src'), suite: 'lint', ) test('ESLint (Installed Tests)', eslint, args: join_paths(meson.project_source_root(), 'installed-tests'), suite: 'lint', ) test('ESLint (WebExtension)', eslint, args: join_paths(meson.project_source_root(), 'webextension'), suite: 'lint', ) endif black = find_program('black', required: false) if black.found() test('Python Black (Nautilus Extension)', black, args: [ '--check', '--diff', join_paths(meson.project_source_root(), 'nautilus-extension') ], suite: 'lint', ) endif flake8 = find_program('flake8', required: false) if flake8.found() test('Python Flake8 (Nautilus Extension)', flake8, args: join_paths(meson.project_source_root(), 'nautilus-extension'), suite: 'lint', ) endif subdir('data') subdir('installed-tests') subdir('nautilus-extension') subdir('po') GSConnect-gnome-shell-extension-gsconnect-43258f9/meson_options.txt000066400000000000000000000043531460766671100255060ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # See https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki/Packaging # GNOME Shell LIBDIR option( 'gnome_shell_libdir', type: 'string', value: '', description: 'LIBDIR for GNOME Shell (eg. $LIBDIR/gnome-shell/Gvc-1.0.typelib)' ) # DBus service file option( 'session_bus_services_dir', type: 'string', value: '', description: 'DBus session services directory' ) # firewalld service file option( 'firewalld', type: 'boolean', value: false, description: 'Install firewalld service file' ) # External program paths # NOTE: these are only useful for distributions like NixOS that don't use PATH option( 'ffmpeg_path', type: 'string', value: 'ffmpeg', description: 'Absolute path to ffmpeg binary' ) option( 'openssl_path', type: 'string', value: 'openssl', description: 'Absolute path to openssl binary' ) option( 'sshadd_path', type: 'string', value: 'ssh-add', description: 'Absolute path to ssh-add binary' ) option( 'sshkeygen_path', type: 'string', value: 'ssh-keygen', description: 'Absolute path to ssh-keygen binary' ) # Nautilus/Nemo Python extension installation option( 'nautilus', type: 'boolean', value: true, description: 'Install file browser extension for Nautilus (Files)' ) option( 'nemo', type: 'boolean', value: false, description: 'Install file browser extension for Nemo' ) # WebExtension manifest installation # NOTE: this is NOT the WebExtension, but is REQUIRED for it to work. option( 'webextension', type: 'boolean', value: true, description: 'Install WebExtension manifest for Chrome, Chromium & Firefox' ) # Override manifest install so that BROWSER_NMHDIR/foo.json option( 'chrome_nmhdir', type: 'string', value: '', description: 'Native Messaging Host directory for Chrome' ) option( 'chromium_nmhdir', type: 'string', value: '', description: 'Native Messaging Host directory for Chromium' ) option( 'mozilla_nmhdir', type: 'string', value: '', description: 'Native Messaging Host directory for Mozilla' ) option( 'installed_tests', type: 'boolean', value: true, description: 'Install tests' ) GSConnect-gnome-shell-extension-gsconnect-43258f9/nautilus-extension/000077500000000000000000000000001460766671100257225ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/nautilus-extension/meson.build000066400000000000000000000007311460766671100300650ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # Nautilus Extension if get_option('nautilus') install_data( 'nautilus-gsconnect.py', install_dir: join_paths(datadir, 'nautilus-python', 'extensions') ) endif # Nemo Extension if get_option('nemo') install_data( 'nautilus-gsconnect.py', rename: join_paths(datadir, 'nemo-python', 'extensions', 'nemo-gsconnect.py') ) endif GSConnect-gnome-shell-extension-gsconnect-43258f9/nautilus-extension/nautilus-gsconnect.py000066400000000000000000000150531460766671100321250ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later """ nautilus-gsconnect.py - A Nautilus extension for sending files via GSConnect. A great deal of credit and appreciation is owed to the indicator-kdeconnect developers for the sister Python script 'kdeconnect-send-nautilus.py': https://github.com/Bajoja/indicator-kdeconnect/blob/master/data/extensions/kdeconnect-send-nautilus.py """ import gettext import os.path import sys import gi gi.require_version("Gio", "2.0") gi.require_version("GLib", "2.0") gi.require_version("GObject", "2.0") from gi.repository import Gio, GLib, GObject # Host application detection # # Nemo seems to reliably identify itself as 'nemo' in argv[0], so we # can test for that. Nautilus detection is less reliable, so don't try. # See https://github.com/linuxmint/nemo-extensions/issues/330 if "nemo" in sys.argv[0].lower(): # Host runtime is nemo-python gi.require_version("Nemo", "3.0") from gi.repository import Nemo as FileManager else: # Otherwise, just assume it's nautilus-python from gi.repository import Nautilus as FileManager SERVICE_NAME = "org.gnome.Shell.Extensions.GSConnect" SERVICE_PATH = "/org/gnome/Shell/Extensions/GSConnect" # Init gettext translations LOCALE_DIR = os.path.join( GLib.get_user_data_dir(), "gnome-shell", "extensions", "gsconnect@andyholmes.github.io", "locale", ) if not os.path.exists(LOCALE_DIR): LOCALE_DIR = None try: i18n = gettext.translation(SERVICE_NAME, localedir=LOCALE_DIR) _ = i18n.gettext except (IOError, OSError) as e: print("GSConnect: {0}".format(str(e))) i18n = gettext.translation( SERVICE_NAME, localedir=LOCALE_DIR, fallback=True ) _ = i18n.gettext class GSConnectShareExtension(GObject.Object, FileManager.MenuProvider): """A context menu for sending files via GSConnect.""" def __init__(self): """Initialize the DBus ObjectManager.""" GObject.Object.__init__(self) self.devices = {} Gio.DBusProxy.new_for_bus( Gio.BusType.SESSION, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, SERVICE_NAME, SERVICE_PATH, "org.freedesktop.DBus.ObjectManager", None, self._init_async, None, ) def _init_async(self, proxy, res, user_data): proxy = proxy.new_for_bus_finish(res) proxy.connect("notify::g-name-owner", self._on_name_owner_changed) proxy.connect("g-signal", self._on_g_signal) self._on_name_owner_changed(proxy, None) def _on_g_signal(self, proxy, sender_name, signal_name, parameters): # Wait until the service is ready if proxy.props.g_name_owner is None: return objects = parameters.unpack() if signal_name == "InterfacesAdded": for object_path, props in objects.items(): props = props["org.gnome.Shell.Extensions.GSConnect.Device"] self.devices[object_path] = ( props["Name"], Gio.DBusActionGroup.get( proxy.get_connection(), SERVICE_NAME, object_path ), ) elif signal_name == "InterfacesRemoved": for object_path in objects: try: del self.devices[object_path] except KeyError: pass def _on_name_owner_changed(self, proxy, pspec): # Wait until the service is ready if proxy.props.g_name_owner is None: self.devices = {} else: proxy.call( "GetManagedObjects", None, Gio.DBusCallFlags.NO_AUTO_START, -1, None, self._get_managed_objects, None, ) def _get_managed_objects(self, proxy, res, user_data): objects = proxy.call_finish(res)[0] for object_path, props in objects.items(): props = props["org.gnome.Shell.Extensions.GSConnect.Device"] if not props: continue self.devices[object_path] = ( props["Name"], Gio.DBusActionGroup.get( proxy.get_connection(), SERVICE_NAME, object_path ), ) def send_files(self, menu, files, action_group): """Send *files* to *device_id*.""" for file in files: variant = GLib.Variant("(sb)", (file.get_uri(), False)) action_group.activate_action("shareFile", variant) def get_file_items(self, *args): """Return a list of select files to be sent.""" # 'args' will depend on the Nautilus API version. # * Nautilus 4.0: # `[files: List[Nautilus.FileInfo]]` # * Nautilus 3.0: # `[window: Gtk.Widget, files: List[Nautilus.FileInfo]]` files = args[-1] # Only accept regular files for uri in files: if uri.get_uri_scheme() != "file" or uri.is_directory(): return () # Enumerate capable devices devices = [] for name, action_group in self.devices.values(): if action_group.get_action_enabled("shareFile"): devices.append([name, action_group]) # No capable devices; don't show menu entry if not devices: return () # If there's exactly 1 device, no submenu if len(devices) == 1: name, action_group = devices[0] menu = FileManager.MenuItem( name="GSConnectShareExtension::Device" + name, # TRANSLATORS: Send to , for file manager # context menu label=_("Send to %s") % name, ) menu.connect("activate", self.send_files, files, action_group) else: # Context Menu Item menu = FileManager.MenuItem( name="GSConnectShareExtension::Devices", label=_("Send To Mobile Device"), ) # Context Submenu submenu = FileManager.Menu() menu.set_submenu(submenu) # Context Submenu Items for name, action_group in devices: item = FileManager.MenuItem( name="GSConnectShareExtension::Device" + name, label=name ) item.connect("activate", self.send_files, files, action_group) submenu.append_item(item) return (menu,) GSConnect-gnome-shell-extension-gsconnect-43258f9/po/000077500000000000000000000000001460766671100224625ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/po/LINGUAS000066400000000000000000000001701460766671100235050ustar00rootroot00000000000000ar be ca cs da de el es et fa fi fr fy gl he hu id it ko lt nl_BE nl pl pt_BR pt ru sk sr@latin sr sv tr uk zh_CN zh_TW GSConnect-gnome-shell-extension-gsconnect-43258f9/po/LINGUAS.license000066400000000000000000000001651460766671100251320ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-43258f9/po/POTFILES000066400000000000000000000030451460766671100236340ustar00rootroot00000000000000data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in data/ui/connect-dialog.ui data/ui/contact-chooser.ui data/ui/contacts-address-row.ui data/ui/legacy-messaging-dialog.ui data/ui/messaging-conversation-message.ui data/ui/messaging-conversation-summary.ui data/ui/messaging-conversation.ui data/ui/messaging-window.ui data/ui/mousepad-input-dialog.ui data/ui/notification-reply-dialog.ui data/ui/preferences-command-editor.ui data/ui/preferences-device-panel.ui data/ui/preferences-section-row.ui data/ui/preferences-shortcut-editor.ui data/ui/preferences-window.ui data/ui/service-device-chooser.ui data/ui/service-error-dialog.ui nautilus-extension/nautilus-gsconnect.py src/extension.js src/preferences/device.js src/preferences/keybindings.js src/preferences/service.js src/service/daemon.js src/service/device.js src/service/manager.js src/service/backends/lan.js src/service/plugins/battery.js src/service/plugins/clipboard.js src/service/plugins/contacts.js src/service/plugins/findmyphone.js src/service/plugins/mousepad.js src/service/plugins/mpris.js src/service/plugins/notification.js src/service/plugins/ping.js src/service/plugins/presenter.js src/service/plugins/runcommand.js src/service/plugins/sftp.js src/service/plugins/share.js src/service/plugins/sms.js src/service/plugins/systemvolume.js src/service/plugins/telephony.js src/service/ui/contacts.js src/service/ui/legacyMessaging.js src/service/ui/messaging.js src/service/ui/mousepad.js src/service/ui/service.js src/shell/device.js src/shell/notification.js webextension/gettext.js GSConnect-gnome-shell-extension-gsconnect-43258f9/po/POTFILES.license000066400000000000000000000001651460766671100252550ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-43258f9/po/ar.po000066400000000000000000001137721460766671100234370ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-08-25 22:45\n" "Last-Translator: \n" "Language-Team: Arabic\n" "Language: ar_SA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: ar\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "تطبيق KDE Connect لجنوم" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "ÙØ±ÙŠÙ‚ GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect هو تنÙيذ كامل لـ KDE Connect خاصة لـ GNOME Shell مع تكامل Nautilus Ùˆ Chrome Ùˆ Firefox. ÙØ±ÙŠÙ‚ KDE Connect لديه تطبيقات لـ Linux Ùˆ BSD Ùˆ Android Ùˆ Sailfish Ùˆ iOS Ùˆ macOS Ùˆ Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "مع GSConnect يمكنك الاتصال الآمن بالأجهزة المحمولة وغيرها من أجهزة المكتب إلى:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "مشاركة Ø§Ù„Ù…Ù„ÙØ§ØªØŒ الروابط والنص" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "إرسال واستقبال الرسائل" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "مزامنة محتوى Ø§Ù„Ø­Ø§ÙØ¸Ø©" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "مزامنة جهات الاتصال" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "مزامنة الإشعارات" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "التحكم ÙÙŠ مشغلات الوسائط" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "التحكم ÙÙŠ مستوى صوت النظام" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "تنÙيذ أوامر محددة مسبقاً" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "والمزيد…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect ÙÙŠ ØµØ¯ÙØ© غنوم" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "الاتصال بـ…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "إلغاء" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "اتصال" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "عنوان الـ IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "لا توجد جهات اتصال" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "المساعدة" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "اكتب رقم هات٠أو اسم" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "أخرى" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "إرسال الرسائل القصيرة" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "إرسال" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "الجهاز غير متصل" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "إرسال رسالة" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "اكتب رسالة" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "إدخال الرسالة" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "اكتب رسالة واضغط على Enter لإرسالها" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "المراسة" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "محادثة جديدة" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "لا توجد محادثات" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "لم يتم تحديد أي محادثة" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "تحديد أو بدء محادثة" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Touchpad.\n" "اسحب على هذه المنطقة لتحريك مؤشر الماوس.\n" "اضغط مطولاً للسحب لسحب مؤشر الماوس.\n\n" "سيتم إرسال النقر البسيط إلى الجهاز المقترن.\n" "التمرير من اليسار والوسط واليمين والعجلة." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "تعديل الأمر" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Ø­ÙØ¸" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "الاسم" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "سطر الأوامر" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "اختر مل٠تنÙيذي" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "ÙØªØ­" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "سطح المكتب" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "الكاميرا" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "مزامنة Ø§Ù„Ø­Ø§ÙØ¸Ø©" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "مشغلات الوسائط" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Ø§Ù„ÙØ£Ø±Ø© ولوحة Ø§Ù„Ù…ÙØ§ØªÙŠØ­" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "التحكم بمستوى الصوت" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Ø§Ù„Ù…Ù„ÙØ§Øª" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "استلام Ù…Ù„ÙØ§Øª" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Ø­ÙØ¸ Ø§Ù„Ù…Ù„ÙØ§Øª إلى" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "المشاركة" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "بطارية الجهاز" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "إشعار Ø§Ù†Ø®ÙØ§Ø¶ البطارية" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "شحن حتى إشعار مستوى مخصص" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "إشعار امتلاء البطارية" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "بطارية الجهاز" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "مشاركة الاحصائيات" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "البطارية" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "اﻷوامر" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Ø¥Ø¶Ø§ÙØ© أمر" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "مشاركة الإشعارات" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "مشاركة عندما يكون نشطا" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "التطبيقات" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "الإشعارات" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "جهات الاتصال" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "المكالمات الواردة" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "مستوى الصوت" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "إيقا٠الوسائط" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "المكالمة الحالية" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "كتم المايكروÙون" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Ø§Ù„Ù…Ù‡Ø§ØªÙØ©" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "اختصارات اﻹجراءات" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "إعادة تعيين الكل…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "الاختصارات" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Ø§Ù„Ø¥Ø¶Ø§ÙØ§Øª" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "تجريبي" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "ذاكرة التخزين المؤقت للجهاز" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "مسح التخزين المؤقت…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "دعم الرسائل النصية القديم" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "توصيل آلي لـ FTP المحمي" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "خيارات متقدمة" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "اختصارات لوحة Ø§Ù„Ù…ÙØ§ØªÙŠØ­" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "إعدادات الجهاز" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "اقتران" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "الجهاز غير مقترن" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "قد تحتاج إلى إعداد هذا الجهاز أولا قبل اﻹقتران" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "معلومات التشÙير" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "إلغاء الاقتران" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "إلى الجهاز" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "من الجهاز" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "لا شيء" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "استعادة" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Ù…Ù†Ø®ÙØ¶" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "كتم" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "تَعيين" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "اضغط على Esc لإلغاء أو إعادة تعيين اختصار لوحة Ø§Ù„Ù…ÙØ§ØªÙŠØ­." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "اسم الجهاز" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_إعادة التسمية" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "تحديث" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "إعدادات الجهاز" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "قائمة الخدمة" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "قائمة الجهاز" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "تعديل اسم الجهاز" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "الأجهزة" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "جار٠البحث عن أجهزة…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "إعدادات الامتداد" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect يبقى نشطاً عند Ù‚ÙÙ„ جنوم شل" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Ø¥Ø¶Ø§ÙØ§Øª Ø§Ù„Ù…ØªØµÙØ­" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "تمكين" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "هذا الجهاز غير مرئي للأجهزة غير المقترنة" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "اكتشا٠معطل" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "وضع العرض" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "لوحة" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "قائمة المستخدم" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "إنشاء سجل الدعم" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "حول GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "تحديد جهاز" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "تحديد" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "لم يتم العثور على أي جهاز" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "قائمة الأجهزة" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "تقرير" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "حدث خطأ ما" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "واجه GSConnect خطأ غير متوقع. يرجى الإبلاغ عن المشكلة وإدراج أي معلومات قد تساعد." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Ø§Ù„ØªÙØ§ØµÙŠÙ„ التقنية" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "إرسال إلى جهاز الجوال" #: src/extension.js:52 msgid "Sync between your devices" msgstr "المزامنة بين أجهزتك" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d متصل" msgstr[1] "%d متصل" msgstr[2] "%d متصل" msgstr[3] "%d متصل" msgstr[4] "%d متصل" msgstr[5] "%d متصل" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "تعديل" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "إزالة" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "معطّل" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "أدخل اختصار جديد لتغيير %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s قيد الاستخدام Ø¨Ø§Ù„ÙØ¹Ù„" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "تطبيق KDE كامل لربط GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "المترجمين" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "يتم تسجيل رسائل تصحيح الأخطاء. اتخاذ أي خطوات ضرورية لإعادة تكرار مشكلة ثم مراجعة السجل." #: src/preferences/service.js:421 msgid "Review Log" msgstr "سجل المراجعة" #: src/preferences/service.js:489 msgid "Laptop" msgstr "حاسوب محمول" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "الهات٠الذكي" #: src/preferences/service.js:493 msgid "Tablet" msgstr "الكمبيوتر اللوحي" #: src/preferences/service.js:495 msgid "Television" msgstr "ØªÙ„ÙØ§Ø²" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "غير مقترن" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "غير متصل" #: src/preferences/service.js:525 msgid "Connected" msgstr "متصل" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "ÙÙŠ انتظار الخدمة…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "انقر للمساعدة ÙÙŠ استكشا٠الأخطاء" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "انقر للحصول على مزيد من المعلومات" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "رقم الطلب" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "مشاركة الملÙ" #: src/service/daemon.js:355 msgid "List available devices" msgstr "قائمة الأجهزة المتاحة" #: src/service/daemon.js:364 msgid "List all devices" msgstr "قائمة جميع الأجهزة" #: src/service/daemon.js:373 msgid "Target Device" msgstr "الجهاز المستهدÙ" #: src/service/daemon.js:415 msgid "Message Body" msgstr "نص الرسالة" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "إرسال إشعار" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "اسم تطبيق الإشعارات" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "نص الإشعار" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "أيقونة الإشعار" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "معر٠الإشعار" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "صورة" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "بينغ" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "رنين" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "مشاركة الرابط" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "مشاركة النص" #: src/service/daemon.js:532 msgid "Show release version" msgstr "إظهار رقم الإصدار" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "جهاز البلوتوث ÙÙŠ %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Ù…ÙØªØ§Ø­ التحقق: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "طلب الاقتران من %s" #: src/service/device.js:850 msgid "Reject" msgstr "Ø±ÙØ¶" #: src/service/device.js:855 msgid "Accept" msgstr "قبول" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "تم تعطيل الاكتشا٠بسبب عدد الأجهزة على هذه الشبكة." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "لم يتم العثور على OpenSSL" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Ø§Ù„Ù…Ù†ÙØ° قيد الاستخدام Ø¨Ø§Ù„ÙØ¹Ù„" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "تبادل معلومات البطارية" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: البطارية ممتلئة" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "مشحونة بالكامل" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: البطارية وصلت إلى مستوى الشحن المخصص" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% مشحون" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: البطارية Ù…Ù†Ø®ÙØ¶Ø©" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% متبقي" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Ø§Ù„Ø­Ø§ÙØ¸Ø©" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "مشاركة محتوى Ø§Ù„Ø­Ø§ÙØ¸Ø©" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Ø¯ÙØ¹ Ø§Ù„Ø­Ø§ÙØ¸Ø©" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "سحب Ø§Ù„Ø­Ø§ÙØ¸Ø©" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "الوصول إلى جهات الاتصال من الجهاز المقترن" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "العثور على هاتÙÙŠ" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "شغّل رنين جهازك المقترن" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "لوحة Ø§Ù„ÙØ£Ø±Ø©" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "تمكين الجهاز المقترن للعمل ÙƒÙØ£Ø±Ø© عن بعد ولوحة Ø§Ù„Ù…ÙØ§ØªÙŠØ­" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "الإدخال عن بعد" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS Ù…ÙˆØ§ØµÙØ§Øª واجهة مشغل الوسائط عن بعد" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "التحكم ÙÙŠ تشغيل الوسائط عن بعد ثنائي الاتجاه" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "غير معروÙ" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "مشاركة الإشعارات مع الجهاز المقترن" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "إلغاء الإشعارات" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "إغلاق الإشعارات" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "الرد على الإشعارات" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "ØªÙØ¹ÙŠÙ„ الإشعارات" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "اطلب من الجهاز المقترن التقاط صورة وتحويلها إلى هذا الكمبيوتر" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "ÙØ´Ù„ التحويل" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "ÙØ´Ù„ إرسال \"%s\" إلى %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "إرسال واستقبال الوخزات" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "الوخز: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "عرض" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "استخدم الجهاز المقترن كمقدم عرض" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "تشغيل الأوامر" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "تشغيل الأوامر على جهازك المقترن أو السماح للجهاز بتشغيل الأوامر المحددة مسبقاً على هذا الكمبيوتر" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "اتصال FTP محمى" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "ØªØµÙØ­ نظام Ù…Ù„ÙØ§Øª الجهاز المقترن" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "توصيل" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "إلغاء التوصيل" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s التبليغ عن خطأ" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "مشاركة" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "مشاركة Ø§Ù„Ù…Ù„ÙØ§Øª والروابط بين الأجهزة" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s غير مسموح له Ø¨Ø±ÙØ¹ Ø§Ù„Ù…Ù„ÙØ§Øª" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "نقل الملÙ" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "تلقي \"%s\" من %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "تم التحويل بنجاح" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "تلقى \"%s\" من %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "إظهار موقع الملÙ" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "ÙØªØ­ ملÙ" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "ÙØ´Ù„ تلقي \"%s\" من %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "نص مشترك من قبل %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "إرسال \"%s\" إلى %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "أرسلت \"%s\" إلى %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "إرسال Ù…Ù„ÙØ§Øª إلى %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "ÙØªØ­ عند الانتهاء" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "إرسال رابط إلى %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "الرسائل القصيرة" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "إرسال وقراءة الرسائل القصيرة للجهاز المقترن مع إعلامك بالرسائل القصيرة الجديدة" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "رسالة نصية جديدة (الرابط)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "الرد كرسالة" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "مشاركة الرسائل القصيرة" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "مستوى صوت النظام" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "تمكين الجهاز المقترن للتحكم ÙÙŠ مستوى صوت النظام" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "لم يتم العثور على PulseAudio" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "يتم إشعاره بالمكالمات وتعديل مستوى صوت النظام أثناء الرنين / المكالمات الجارية" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "كتم المكالمة" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "جهة اتصال غير Ù…Ø¹Ø±ÙˆÙØ©" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "مكالمة واردة" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "مكالمة جارية" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "ÙØ§ÙƒØ³" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "العمل" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "الجوال" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Ø§Ù„ØµÙØ­Ø© الرئيسية" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "إرسال إلى %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "للتو الآن" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "أمس، %s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d دقيقة" msgstr[1] "%d دقيقة" msgstr[2] "%d دقائق" msgstr[3] "%d دقائق" msgstr[4] "%d دقائق" msgstr[5] "%d دقائق" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "غير Ù…ÙـتوÙـّر" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "رسالة جماعية" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "أنت: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Ùˆ %d جهة اتصال أخرى" msgstr[1] "Ùˆ %d جهة اتصال أخرى" msgstr[2] "Ùˆ %d آخرين" msgstr[3] "Ùˆ %d آخرين" msgstr[4] "Ùˆ %d آخرين" msgstr[5] "Ùˆ %d آخرين" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "لوحة Ø§Ù„Ù…ÙØ§ØªÙŠØ­ البعيدة ÙÙŠ %s غير نشطة" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (تقدير…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d حتى الاكتمال)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d متبقي)" #: src/shell/notification.js:58 msgid "Reply" msgstr "الرد" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "مشاركة الروابط مع GSConnectØŒ مباشرة إلى Ø§Ù„Ù…ØªØµÙØ­ أو بواسطة الرسائل القصيرة." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "الخدمة غير متاحة" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "ÙØªØ­ ÙÙŠ Ø§Ù„Ù…ØªØµÙØ­" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/be.po000066400000000000000000001157031460766671100234170ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Belarusian\n" "Language: be_BY\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%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || n%10>=5 && n%10<=9 || n%100>=11 && n%100<=14 ? 2 : 3);\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: be\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "РÑÐ°Ð»Ñ–Ð·Ð°Ñ†Ñ‹Ñ KDE Connect Ð´Ð»Ñ GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Каманда GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "З дапамогай GSConnect вы можаце бÑÑпечна падключацца да мабільных Ñ– іншых ÑтацыÑнарных прылады, каб:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Ðбагульваць файлы, ÑпаÑылкі Ñ– Ñ‚ÑкÑÑ‚" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "ÐдпраўлÑць Ñ– прымаць паведамленні" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Сінхранізаваць змеÑціва буфера абмену" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Сінхранізаваць кантакты" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Сінхранізаваць апавÑшчÑнні" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Кіраваць медыÑпрайгравальнікамі" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Кіраваць гучнаÑцю ÑÑ–ÑÑ‚Ñмы" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Выконваць папÑÑ€Ñдне Ð·Ð°Ð´Ð°Ð´Ð·ÐµÐ½Ñ‹Ñ ÐºÐ°Ð¼Ð°Ð½Ð´Ñ‹" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "І іншае…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect у GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Падлучыцца да…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "СкаÑаваць" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "ЗлучÑнне" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP-адраÑ" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "ÐÑма кантактаў" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Даведка" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "УвеÑці нумар Ñ‚Ñлефона або імÑ" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Іншае" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Ðдправіць SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Ðдправіць" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Прылада адлучана" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Ðдправіць паведамленне" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "ÐапіÑаць паведамлене" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Ð—Ð°Ð¿Ñ–Ñ Ð¿Ð°Ð²ÐµÐ´Ð°Ð¼Ð»ÐµÐ½Ð½Ñ" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "УвÑдзіце паведамленне націÑніце Enter Ð´Ð»Ñ Ð°Ð´Ð¿Ñ€Ð°ÑžÐºÑ–" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Паведамленні" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "ÐÐ¾Ð²Ð°Ñ Ñ€Ð°Ð·Ð¼Ð¾Ð²Ð°" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "ÐÑма размоў" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Ðе выбрана размова" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Выберыце або пачніце размову" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "РÑдагаваць каманду" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Захаваць" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Ðазва" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Камандны радок" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Выбраць выконвальны файл" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Ðдкрыць" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Камп'ютар" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Камера" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Ð¡Ñ–Ð½Ñ…Ñ€Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ‹Ñ Ð±ÑƒÑ„ÐµÑ€Ñƒ абмену" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "МедыÑпрайгравальнікі" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Мыш Ñ– клавіÑтура" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Кіраванне гучнаÑцю" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Файлы" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Ðтрымліваць файлы" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Захаваць файлы Ñž" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Ðбагульванне" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "ÐкумулÑтар прылады" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "ÐпавÑшчÑнне пра нізкі ўзровень зараду акумулÑтара" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "ÐпавÑшчаць пра зададзены ўзровень зараду" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "ÐпавÑшчÑнне пра поўнаÑцю зараджаны акумулÑтар" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "СіÑÑ‚Ñмны акумулÑтар" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Ðбагульваць ÑтатыÑтыку" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "ÐкумулÑтар" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Каманды" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Дадаць каманду" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Ðбагульваць апавÑшчÑнні" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Ðбагульваць, калі актыўны" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Праграмы" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "ÐпавÑшчÑнні" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Кантакты" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Ð£Ð²Ð°Ñ…Ð¾Ð´Ð½Ñ‹Ñ Ð²Ñ‹ÐºÐ»Ñ–ÐºÑ–" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Гук" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Прыпыніць прайграванне" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Ð’Ñ‹Ñ…Ð¾Ð´Ð½Ñ‹Ñ Ð²Ñ‹ÐºÐ»Ñ–ÐºÑ–" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Ðдключыць мікрафон" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "ТÑлефаніÑ" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "СпалучÑнні клавіш Ð´Ð»Ñ Ð´Ð·ÐµÑннÑÑž" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Скінуць уÑе…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "СпалучÑнні клавіш" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Убудовы" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "ЭкÑперыментальныÑ" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "КÑш прылады" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "ÐчыÑтка кÑшу…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Падтрымка SMS (Ñ€Ð°Ð½ÐµÐ¹ÑˆÐ°Ñ Ð²ÐµÑ€ÑÑ–Ñ)" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "ÐўтападлучÑнне SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "ПашыраныÑ" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "СпалучÑнні клавіш" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Ðалады прылады" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Спалучыць" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Прылада разлучана" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Ð’Ñ‹ можаце наладзіць гÑту прыладу перад ÑпалучÑннем" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Ð†Ð½Ñ„Ð°Ñ€Ð¼Ð°Ñ†Ñ‹Ñ Ð¿Ñ€Ð° шыфраванне" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Разлучыць" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Ðа прыладу" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "З прылады" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Ðічога" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Ðднавіць" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "ÐіжÑйшы" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Выключыць гук" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Задаць" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "ÐаціÑніце Esc, каб ÑкаÑаваць або Прабел, каб Ñкінуць ÑпалучÑнне клавіш." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Ðазва прылады" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Перайменаваць" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Ðбнавіць" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Ðалады прылад" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "СÑрвіÑнае меню" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Меню прылады" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "РÑдагаваць назву прылады" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Прылады" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Пошук прылад…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "ПашырÑнні Ð´Ð»Ñ Ð±Ñ€Ð°ÑžÐ·ÐµÑ€Ð°" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Уключана" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "ГÑта прылада нÑÐ±Ð°Ñ‡Ð½Ð°Ñ Ð´Ð»Ñ Ñ€Ð°Ð·Ð»ÑƒÑ‡Ð°Ð½Ñ‹Ñ… прылад" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Знаходжанне адключана" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "РÑжым паказу" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "ПанÑль" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Меню карыÑтальніка" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Стварыць журнал Ð´Ð»Ñ Ð¿Ð°Ð´Ñ‚Ñ€Ñ‹Ð¼ÐºÑ–" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Пра GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Выбраць прыладу" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Выбраць" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Ðе знойдзена прылад" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Ð¡Ð¿Ñ–Ñ Ð¿Ñ€Ñ‹Ð»Ð°Ð´" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Справаздача" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Ðешта пайшло не так" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "У GSConnect адбылаÑÑ Ð½ÐµÑ‡Ð°ÐºÐ°Ð½Ð°Ñ Ð¿Ð°Ð¼Ñ‹Ð»ÐºÐ°. Паведаміце аб праблеме разам з любой інфармацыÑй, ÑÐºÐ°Ñ Ð¼Ð¾Ð¶Ð° дапамагчы." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "ТÑÑ…Ð½Ñ–Ñ‡Ð½Ñ‹Ñ Ð¿Ð°Ð´Ñ€Ð°Ð±ÑзнаÑці" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Ðдправіць на мабільную прыладу" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d падлучана" msgstr[1] "%d падлучаны" msgstr[2] "%d падлучана" msgstr[3] "%d падлучана" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "РÑдагаваць" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Выдаліць" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Ðдключана" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "УвÑдзіце Ð½Ð¾Ð²Ñ‹Ñ ÑпалучÑнні, каб змÑніць %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s ужо выкарыÑтоўваецца" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "ПаўнавартаÑÐ½Ð°Ñ Ñ€ÑÐ°Ð»Ñ–Ð·Ð°Ñ†Ñ‹Ñ KDE Connect Ð´Ð»Ñ GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Maksim KrapiÅ­ka " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Паведамленні адладкі былі запіÑаны Ñž журнал. Зрабіце Ð»ÑŽÐ±Ñ‹Ñ ÐºÑ€Ð¾ÐºÑ– Ð½ÐµÐ°Ð±Ñ…Ð¾Ð´Ð½Ñ‹Ñ Ð´Ð»Ñ ÑžÐ·Ð½Ð°ÑžÐ»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð°Ð±Ð»ÐµÐ¼Ñ‹ Ñ– затым праглÑдзіце журнал." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Праверыць журнал" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Ðоўтбук" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Смартфон" #: src/preferences/service.js:493 msgid "Tablet" msgstr "ПланшÑÑ‚" #: src/preferences/service.js:495 msgid "Television" msgstr "ТÑлебачанне" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Разлучана" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Ðдлучана" #: src/preferences/service.js:525 msgid "Connected" msgstr "Падлучана" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Чаканне ÑÑрвіÑу…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "ÐаціÑніце Ð´Ð»Ñ Ð´Ð°Ð¿Ð°Ð¼Ð¾Ð³Ñ– Ñž выпраўленні непаладак" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "ÐаціÑніце Ð´Ð»Ñ Ð±Ð¾Ð»ÑŒÑˆÐ°Ð¹ інфармацыі" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Ðабраць нумар" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Ðбагуліць файл" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Ð¡Ð¿Ñ–Ñ Ð´Ð°Ñтупных прылад" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Ð¡Ð¿Ñ–Ñ ÑƒÑÑ–Ñ… прылад" #: src/service/daemon.js:373 msgid "Target Device" msgstr "МÑÑ‚Ð°Ð²Ð°Ñ Ð¿Ñ€Ñ‹Ð»Ð°Ð´Ð°" #: src/service/daemon.js:415 msgid "Message Body" msgstr "ЗмеÑÑ‚ паведамленнÑ" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Ðдправіць апавÑшчÑнне" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Ðазва праграмы Ñž апавÑшчÑнні" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "ЗмеÑÑ‚ апавÑшчÑннÑ" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Значок апавÑшчÑннÑ" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ІдÑнтыфікатар апавÑшчÑннÑ" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Фота" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Пінг" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Званок" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Ðбагуліць ÑпаÑылку" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Ðбагуліць Ñ‚ÑкÑÑ‚" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Паказаць верÑÑ–ÑŽ праграмы" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth прылада па %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Запыт ÑпалучÑÐ½Ð½Ñ Ð°Ð´ %s" #: src/service/device.js:850 msgid "Reject" msgstr "Ðдхіліць" #: src/service/device.js:855 msgid "Accept" msgstr "ПрынÑць" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Знаходжанне было адключана праз колькаÑць прылад у гÑтай Ñетцы." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL не знойдзены" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Порт ужо выкарыÑтоўваецца" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Ðбмен інфармацыÑй пра акумулÑтар" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: акумулÑтар зараджаны" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Зараджана цалкам" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: акумулÑтар даÑÑгнуў зададзенага ўзроўню зараду" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% зараджана" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: нізкі зарад акумулÑтара" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% заÑталоÑÑ" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Буфер абмену" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Ðбагульваць змеÑціва буфера абмену" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Ðдправіць буфер абмену" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "ЗапраÑіць буфер абмену" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "ДоÑтуп да кантактаў Ñпалучанай прылады" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Ðдшукаць мой Ñ‚Ñлефон" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Ðдгукацца Ñпалучанай прыладай" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Тачпад" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "ДазвалÑе Ñпалучанай прыладзе дзейнічаць Ñк аддаленай мышшу Ñ– клавіÑтурай" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Двухнакіраванае аддаленае кіраванне прайграваннем" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "ÐевÑдомы" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Ðбагульваць апавÑшчÑнні Ñа Ñпалучанымі прыладамі" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "СкаÑаваць апавÑшчÑнне" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Закрыць апавÑшчÑнне" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Ðдказаць на апавÑшчÑнне" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Ðктываваць апавÑшчÑнне" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Запытваць Ñпалучаную прыладу на здымку фота Ñ– перадачу Ñго на гÑты ПК" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Збой перадачы" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Збой адпраўкі “%s†у %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "ÐдпраўлÑць Ñ– атрымліваць пінг" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Пінг: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "ПрÑзентацыÑ" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "ВыкарыÑтоўваць Ñпалучаную прыладу Ð´Ð»Ñ Ð¿Ñ€Ñзентацыі" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "ЗапуÑкаць каманды" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "ЗапуÑкаць каманды на Ñпалучанай прыладзе або дазволіць ёй запуÑкаць Ð¿Ñ€Ð°Ð´Ð²Ñ‹Ð·Ð½Ð°Ñ‡Ð°Ð½Ñ‹Ñ ÐºÐ°Ð¼Ð°Ð½Ð´Ñ‹ на гÑтым ПК" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "ПраглÑдаць файлавую ÑÑ–ÑÑ‚Ñму Ñпалучанай прылады" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Падлучыць" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Ðдлучыць" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s паведаміў пра памылку" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Ðбагуліць" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Ðбагульваць файлы Ñ– ÑпаÑылкі паміж прыладамі" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s не дазвалÑе запампоўваць файлы" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Перадача файла" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Ðтрыманне “%s†з %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "ПаÑпÑхова перададзена" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Ðтрымана “%s†з %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Ðдкрыць файл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Ðе ўдалоÑÑ Ð°Ñ‚Ñ€Ñ‹Ð¼Ð°Ñ†ÑŒ “%s†з %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "ТÑкÑÑ‚ абагулены %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Ðдпраўка “%s†у %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Ðдпраўлена “%s†у %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Ðдправіць файлы Ñž %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Ðдкрыць па ÑканчÑнні" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Ðдправіць ÑпаÑылку Ñž %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "ÐдпраўлÑць Ñ– чытаць SMS на Ñпалучанай прыладзе, апавÑшчаць пра Ð½Ð¾Ð²Ñ‹Ñ SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Ðовае SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Ðдказаць на SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Ðбагуліць SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "СіÑÑ‚ÑÐ¼Ð½Ð°Ñ Ð³ÑƒÑ‡Ð½Ð°Ñць" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Дазволіць Ñпалучанай прыладзе кіраваць гучнаÑцю ÑÑ–ÑÑ‚Ñмы" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio не знойдзены" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "ÐпавÑшчаць пра выклікі Ñ– Ñ€ÑгулÑваць гучнаÑць ÑÑ–ÑÑ‚Ñмы Ð¿Ð°Ð´Ñ‡Ð°Ñ Ð²Ñ‹ÐºÐ»Ñ–ÐºÐ°Ñž" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Ðдкл. гук выкліку" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "ÐевÑдомы кантакт" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Уваходны выклік" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Выходны выклік" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "ФакÑ" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Працоўны" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Мабільны" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Дамашні" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Ðдправіць у %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Толькі што" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Учора・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d хвіліна" msgstr[1] "%d хвіліны" msgstr[2] "%d хвілін" msgstr[3] "%d хвіліны" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "ÐедаÑтупны" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Групавое паведамленне" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Ð’Ñ‹: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "І ÑÑˆÑ‡Ñ %d кантакт" msgstr[1] "І %d Ñ–Ð½ÑˆÑ‹Ñ ÐºÐ°Ð½Ñ‚Ð°ÐºÑ‚Ñ‹" msgstr[2] "І %d іншых кантактаў" msgstr[3] "І %d іншых" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "ÐÐ´Ð´Ð°Ð»ÐµÐ½Ð°Ñ ÐºÐ»Ð°Ð²Ñ–Ñтура %s не актыўнаÑ" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (ÐцÑнка…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d Да поўнага зараду)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d заÑтаецца)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Ðдказаць" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Ðбагульвайце ÑпаÑылкі з GSConnect, напрамую Ñž браўзер або праз SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "СÑÑ€Ð²Ñ–Ñ Ð½ÐµÐ´Ð°Ñтупны" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Ðдкрыць у браўзеры" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/ca.po000066400000000000000000001035031460766671100234070ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-09-25 07:38\n" "Last-Translator: \n" "Language-Team: Catalan\n" "Language: ca_ES\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: ca\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementació del KDE Connect per al GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "L’equip del GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect és una implementació completa de KDE Connect especialment per a GNOME Shell amb integració amb Nautilus, Chrome i Firefox. L'equip de KDE Connect té aplicacions per a Linux, BSD, Android, Sailfish, iOS, macOS i Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Amb el GSConnect podeu connectar-vos de forma segura a dispositius mòbils i altres escriptoris per a:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Compartir fitxers, enllaços i text" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Enviar i rebre missatges" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Sincronitzar el contingut del porta-retalls" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Sincronitzar els contactes" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Sincronitzar les notificacions" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Controlar reproductors multimèdia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Controlar el volum del sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Executar ordres predefinides" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "I més…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect en el GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Connecta amb…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Cancel·la" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Connecta" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Adreça IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "No hi ha cap contacte" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Ajuda" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Introduïu un número telefònic o un nom" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Altres" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Envia un SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Envia" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "El dispositiu està desconnectat" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Envia el missatge" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Escriviu un missatge" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Escriu un missatge i apreta Enter per enviar" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Missatgeria" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Conversa nova" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "No hi ha cap conversa" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "No s’ha seleccionat cap conversa" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Seleccioneu una conversa o inicieu-ne una de nova" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Edita l’ordre" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Desa" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nom" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Línia d’ordres" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Trieu un executable" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Obre" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Ordinador d’escriptori" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Càmera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Sincronització del porta-retalls" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Reproductors multimèdia" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Ratolí i teclat" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Control de volum" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Fitxers" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Rebre fitxers" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Desa els fitxers a" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Compartició" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Bateria del dispositiu" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Notificació de bateria baixa" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Notificació de bateria carregada" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Bateria del sistema" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Comparteix estadístiques" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Bateria" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Ordres" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Afegeix una ordre" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Notificacions de compartició" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Comparteix quan estigui actiu" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Aplicacions" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notificacions" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contactes" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Trucades entrants" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volum" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Posa en pausa la multimèdia" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Trucades en curs" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Silencia el micròfon" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonia" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Dreceres d’accions" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Reinicialitza-ho tot…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Dreceres" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Connectors" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experiments" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Memòria cau del dispositiu" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Neteja la memòria cau…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Compatibilitat d’SMS llegat" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Muntatge automàtic d’SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avançat" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Dreceres de teclat" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Paràmetres del dispositiu" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Aparella" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "No s’ha aparellat el dispositiu" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Podeu configurar el dispositiu abans d’aparellar-lo" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informació de xifratge" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Desaparella" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Al dispositiu" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Des del dispositiu" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Res" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Restaura" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Abaixa" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Silencia" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Defineix" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Premeu Esc per a cancel·lar o Retrocés per a reinicialitzar la drecera de teclat." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nom del dispositiu" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Canvia el nom" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Actualitza" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Paràmetres del mòbil" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menú de serveis" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menú d’aparells" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Edita el nom del dispositiu" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Dispositius" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "S’estan cercant dispositius…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Configuració de l'extensió" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect es manté actiu quan GNOME Shell està bloquejat" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Connectors per als navegadors" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Activa" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Aquest dispositiu és invisible als dispositius no aparellats" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Descobriment deshabilitat" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Mode de visualització" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Plafó" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Menú d’usuari" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Genera un registre d'assistència" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Quant al GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Seleccioneu un dispositiu" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Selecciona" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "No s’ha trobat cap dispositiu" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Llista de dispositius" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Informar d'un error" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Alguna cosa ha anat malament" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect ha trobat un error inesperat. Si us plau, informa del problema i inclou qualsevol informació que pugui ajudar." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Detalls tècnics" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Envia al dispositiu mòbil" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Sincronitza entre els teus dispositius" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d connectat" msgstr[1] "%d connectats" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Edita" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Elimina" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Desactivat" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s ja s’està fent servir" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Una implementació completa de KDE Connect per al GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Adolfo Jayme Barrientos , 2019-2020\n" "Marc Riera Irigoyen , 2019" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "S’estan registrant els missatges de depuració. Efectueu els passos necessaris per a reproduir un problema i, tot seguit, reviseu el registre." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Mostra el registre" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Ordinador portàtil" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Telèfon intel·ligent" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tauleta" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisió" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Desaparellat" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Desconnectat" #: src/preferences/service.js:525 msgid "Connected" msgstr "Connectat" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "S’està esperant el servei…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Ajuda per a resoldre el problema" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Més informació" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Comparteix un fitxer" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Enumera els dispositius disponibles" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Enumera tots els dispositius" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Dispositiu de destinació" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Cos del missatge" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Envia una notificació" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Nom d’aplicació de la notificació" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Corps de la notificació" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Icona de notificació" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Identificador de la notificació" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Comprovació de connectivitat" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Truca" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Comparteix un enllaç" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Comparteix text" #: src/service/daemon.js:532 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositiu Bluetooth a %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Clau de verificació: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Sol·licitud d’aparellament d’un %s" #: src/service/device.js:850 msgid "Reject" msgstr "Rebutja-la" #: src/service/device.js:855 msgid "Accept" msgstr "Accepta-la" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "No s’ha trobat l’OpenSSL" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Càrrega completa" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% restant" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Porta-retalls" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Troba el meu telèfon" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Desconegut" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Ha fallat la transferència" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "No s’ha pogut enviar «%s» al %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Comprovació de connectivitat: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Presentació" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Execució d’ordres" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Munta" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Desmunta" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Compartició" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "S’està rebent «%s» del %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "La transferència ha estat exitosa" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "S’ha rebut «%s» del %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Mostra la ubicació del fitxer" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Obre el fitxer" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "No s’ha pogut rebre «%s» del %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Text compartit per %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "S’està enviant «%s» al %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "S’ha enviat «%s» al %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Envia fitxers al %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Obre’l en finalitzar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Envia un enllaç al %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "SMS nou (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volum del sistema" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "No s’ha trobat el PulseAudio" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silencia la trucada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Contacte desconegut" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Trucada entrant" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Trucada en curs" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Feina" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mòbil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Particular" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Envia al %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Ara mateix" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Ahir・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minuts" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "No disponible" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Missatge de grup" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Vós: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "I %d altre contacte" msgstr[1] "I %d d’altres" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (en estimació…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d restants)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Respon" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Compartiu enllaços amb el GSConnect, directament al navegador o mitjançant SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "El servei no està disponible" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Obre al navegador" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/cs.po000066400000000000000000001073711460766671100234400ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-30 21:24\n" "Last-Translator: \n" "Language-Team: Czech\n" "Language: cs_CZ\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==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: cs\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementace KDE Connect pro GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Kolektiv GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect je kompletní implementace nástroje KDE Connect, urÄená zejména pro prostÅ™edí GNOME Shell s napojením pro správce souborů Nautilus a dále pro webové prohlížeÄe Chrome a Firefox. Tým KDE Connect má aplikace pro systémy Linux, BSD, Android, Sailfish, iOS, macOS a Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Pomocí GSConnect se můžete bezpeÄnÄ› pÅ™ipojit k mobilním zařízením a ostatním stolním poÄítaÄům a:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Sdílet soubory, odkazy a text" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Odesílat a pÅ™ijímat zprávy" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Synchronizovat obsah schránky" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Synchronizovat kontakty" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Synchronizovat oznámení" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Ovládat pÅ™ehrávání multimédií" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Ovládat hlasitost systému" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "SpouÅ¡tÄ›t pÅ™edpÅ™ipravené příkazy" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "A další…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect v GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "PÅ™ipojit k…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Storno" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "PÅ™ipojit" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP adresa" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Žádné kontakty" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "NápovÄ›da" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "NapiÅ¡te telefonní Äíslo nebo jméno" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Ostatní" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Poslat SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Poslat" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Zařízení je odpojeno" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Odeslat zprávu" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Napsat zprávu" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Zadání zprávy" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "NapiÅ¡te zprávu a stisknutím klávesy Enter ji odeÅ¡lete" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Zasílání zpráv" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nová konverzace" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Žádné konverzace" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nevybrána žádná konverzace" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Vybrat nebo zahájit konverzaci" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Touchpad.\n" "PÅ™etáhnÄ›te tuto oblast pro pÅ™esun kurzoru myÅ¡i.\n" "StisknÄ›te dlouze pro pÅ™etažení kurzoru.\n\n" "Jednoduché kliknutí bude odesláno do spárovaného zařízení.\n" "Levé, prostÅ™ední, pravé a posouvací koleÄko." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Upravit příkaz" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Uložit" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Název" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Příkazový řádek" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Vyberte spustitelný soubor" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Otevřít" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "PoÄítaÄ" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Fotoaparát" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Synchronizace schránky" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "PÅ™ehrávaÄe médií" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "MyÅ¡ a klávesnice" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Ovládání hlasitosti" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Soubory" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "PÅ™ijmout soubory" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Uložit soubory do" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Sdílení" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Akumulátor zařízení" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "UpozornÄ›ní na téměř vybitý akumulátor" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "ZmÄ›nÄ›no na uživatelsky urÄenou úroveň upozorňování" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Oznámení o úplném dobití" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Systémový akumulátor" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Sdílet statistiky" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Akumulátor" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Příkazy" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "PÅ™idat příkaz" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Sdílet oznámení" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Sdílet když je aktivní" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Aplikace" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Oznámení" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontakty" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Příchozí hovory" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Hlasitost" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Pozastavit multimédia" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Probíhající hovory" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Ztlumit mikrofon" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonie" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Klávesové zkratky akcí" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Vrátit vÅ¡e na výchozí hodnoty…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Klávesové zkratky" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Zásuvné moduly" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimentální" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Mezipaměť zařízení" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Vymazat mezipaměť…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Podpora původních SMS zpráv" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFTP automatické pÅ™ipojení" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "PokroÄilé" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Klávesové zkratky" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Nastavení zařízení" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Spárování" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Zařízení není spárováno" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "PÅ™ed spárováním může být tÅ™eba toto zařízení nastavit" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informace o Å¡ifrování" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "ZruÅ¡it spárování" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Do zařízení" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Ze zařízení" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Nic" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Obnovit" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Snížit" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Ztlumit" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Nastavit" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Zrušíte stiskem Esc nebo pomocí Backspace vrátíte do původního stavu." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Název zařízení" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_PÅ™ejmenovat" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Obnovit" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Nastavení telefonu" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Nabídka služeb" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Nabídka zařízení" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Upravit název zařízení" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Zařízení" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Vyhledávání zařízení…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Nastavení rozšíření" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect zůstává aktivní, když je GNOME Shell uzamÄen" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Doplňky pro prohlížeÄe" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Zapnout" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Toto zařízení je pro nespárovaná zařízení neviditelné" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Objevování vypnuto" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Režim zobrazení" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Uživatelská nabídka" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "VytvoÅ™it záznam událostí pro podporu" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "O GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Vybrat zařízení" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Vybrat" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Nenalezeno žádné zařízení" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Seznam zařízení" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Hlášení" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "NÄ›co se pokazilo" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect narazilo na neoÄekávanou chybu. Nahlaste prosím problém a pÅ™iložte veÅ¡keré informace, které mohou pomoci." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Technické podrobnosti" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Odeslat do mobilního zařízení" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Synchronizace mezi zařízeními" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d pÅ™ipojen" msgstr[1] "%d pÅ™ipojené" msgstr[2] "%d pÅ™ipojených" msgstr[3] "%d pÅ™ipojené" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Upravit" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Odebrat" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Vypnuto" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Zadejte novou zkratku pro %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s už je používáno" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Úplná (re)implementace KDE Connect pro GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Pavel Borecki " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Ladící zprávy jsou zaznamenávány. PodniknÄ›te kroky, potÅ™ebné pro vyvolání problému a pak si záznam prohlédnÄ›te." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Prohlédnout si záznam událostí" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Notebook" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Chytrý telefon" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Televize" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Nespárováno" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Odpojeno" #: src/preferences/service.js:525 msgid "Connected" msgstr "PÅ™ipojeno" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "ÄŒeká se na službu…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Kliknutím otevÅ™ete nápovÄ›du pro odstraňování potíží" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Další informace získáte kliknutím" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "VytoÄit Äíslo" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Sdílet soubor" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Vypsat zařízení k dispozici" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Vypsat vÅ¡echna zařízení" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Cílové zařízení" #: src/service/daemon.js:415 msgid "Message Body" msgstr "TÄ›lo zprávy" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Poslat oznámení" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Název oznamovací aplikace" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "TÄ›lo oznámení" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Ikona oznámení" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Identif. oznámení" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Fotka" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "VyzvánÄ›ní" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Sdílet odkaz" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Sdílet text" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Zobrazit verzi vydání" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth zařízení na %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Ověřovací klíÄ: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Žádost o spárování od %s" #: src/service/device.js:850 msgid "Reject" msgstr "Odmítnout" #: src/service/device.js:855 msgid "Accept" msgstr "PÅ™ijmout" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Kvůli velkému množství zařízení na této síti bylo objevování vypnuto." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL nebylo nalezeno" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Port už je používán" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "UpÅ™esňující informace o akumulátoru" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Akumulátor je nabitý" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "PlnÄ› nabito" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Akumulátor dosáhl uživatelsky urÄené úrovnÄ› nabití" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% nabito" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Akumulátor je téměř vybitý" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% zbývá" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Schránka" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Sdílet obsah schránky" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Odesílání schránky" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Stahování schránky" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "PÅ™istupovat ke kontaktům na spárovaném zařízení" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Najít můj telefon" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Prozvonit vaÅ¡e spárované zařízení" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "MyÅ¡" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Umožní použít spárované zařízení jako vzdálenou myÅ¡ a klávesnici" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Vzdálený vstup" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "ObousmÄ›rné dálkové ovládání pÅ™ehrávání multimédií" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Neznámé" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Sdílet oznámení se spárovaným zařízením" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "ZruÅ¡it oznámení" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Zavřít oznámení" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "OdpovÄ›dÄ›t na oznámení" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Zapnout oznamování" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Vyžádat si pořízení fotky spárovaným zařízením a pÅ™enést ji do tohoto poÄítaÄe" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "PÅ™enos se nezdaÅ™il" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "NepodaÅ™ilo se odeslat „%s“ do %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Odeslat a pÅ™ijmout pingy" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Prezentace" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Použít spárované zařízení jako prezentátor" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Spustit příkazy" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Spustit příkazy na svém spárovaném zařízení nebo nechat zařízení spouÅ¡tÄ›t pÅ™edem urÄené příkazy na tomto poÄítaÄi" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Procházet souborový systém spárovaného zařízení" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "PÅ™ipojit" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Odpojit" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s nahlásilo chybu" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Sdílet" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Sdílet soubory a URL adresy mezi zařízeními" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s není umožnÄ›no nahrávat soubory" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "PÅ™enášení souboru" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Získávání „%s“ z %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "PÅ™enos úspěšný" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Obdrženo „%s“ z %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Zobrazit umístÄ›ní souboru" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Otevřít soubor" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "NepodaÅ™ilo se pÅ™ijmout „%s“ od %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Text sdílený %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Posílání „%s“ do %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "„%s“ odesláno do %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Odeslat soubory do %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Po dokonÄení otevřít" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Poslat odkaz do %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Poslat a Äíst SMS zprávu ze spárovaného zařízení a být upozornÄ›ni na nové SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nová SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "OdpovÄ›dÄ›t na SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Sdílet SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Hlasitost systému" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Umožnit ovládat ze spárovaného zařízení hlasitost zvuku na systému" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio nenalezeno" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Dostávejte upozornÄ›ní na hovory a upravujte hlasitost poÄítaÄi pÅ™i vyzvánÄ›ní / probíhajících hovorech" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "UmlÄet hovor" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Neznámý kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Příchozí hovor" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Probíhající hovor" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Pracovní" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobilní" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Domů" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Poslat na %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "PrávÄ› teÄ" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "VÄera・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuta" msgstr[1] "%d minuty" msgstr[2] "%d minut" msgstr[3] "%d minut" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Není k dispozici" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Skupinová zpráva" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Vy: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "A %d další kontakt" msgstr[1] "A %d další kontakty" msgstr[2] "A %d dalších kontaktů" msgstr[3] "A %d další kontakty" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Vzdálená klávesnice není na %s aktivní" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (odhaduje se…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d do úplného nabití)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d zbývá)" #: src/shell/notification.js:58 msgid "Reply" msgstr "OdpovÄ›dÄ›t" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Sdílet odkazy pomocí GSConnect, přímo do prohlížeÄe nebo prostÅ™ednictvím SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Služba nedostupná" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Otevřít v prohlížeÄi" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/da.po000066400000000000000000001005641460766671100234140ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Danish\n" "Language: da_DK\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: da\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect hold" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Med GSConnect du kan tilslut sikkert til mobil enheder og andre stationær computer til:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Send og modtag beskeder" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Synkroniser udklipsholder indhold" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Synkroniser kontakter" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Synkroniser notifikationer" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Og mere…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect i GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Forbind til…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Annuller" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Forbind" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP adresse" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Ingen kontakter" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Hjælp" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Indtast et telefonnummer eller navn" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Andre" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Send SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Send" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Enheden er afbrudt" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Send Meddelelse" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Skriv en meddelelse" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Besked Input" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Messaging" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Ny Samtale" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Ingen Samtaler" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Ingen samtaler valgt" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Vælg eller start en samtale" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Rediger Kommando" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Gem" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Navn" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Kommandolinje" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Vælg en eksekverbar" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Ã…bn" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Stationær Computer" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Udklipsholder synkroniseres" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Medieafspillere" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Mus & Tastatur" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Lydstyring" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Filer" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Modtag Filer" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Gem filer til" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Deling" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Enhedens Batteri" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Lavt Batteri Notifikation" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Fuldt Opladet Notifikation" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "System Batteri" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Del Statistikker" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Batteri" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Kommandoer" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Tilføj Kommando" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Del Notifikationer" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Del NÃ¥r Aktiv" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Applikationer" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notifikationer" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontakter" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "IndgÃ¥ende Opkald" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Lydstryke" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Pause Media" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Igangværende Opkald" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Stum Mikrofon" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefoni" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Handlings Genveje" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Nulstil Alle…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Genveje" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Plugins" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Eksperimentel" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Enhed Cache" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Ryd Cache…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Arv SMS" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avanceret" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Tastaturgenveje" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Enhed Indstillinger" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Par" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Enheden er uparret" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Du kan konfigurere denne enhed før parring" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Kryptering Info" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Koble fra en enhed" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Til Apparat" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Fra Enhed" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Intet" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Gendan" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Reducere" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Lydløs" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Indstil" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Tryk pÃ¥ Esc at annullere eller backspace-nøgle for at nulstille tastaturgenvejen." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Enheds Navn" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Omdøbe" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Opdater" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobile Indstillinger" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Service Menu" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Enhedsmenu" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Rediger Enheds Navn" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Enheder" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Søger efter enheder…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Webbrowser Tilføj-Ons" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Aktiver" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Denne enhed er usynlig til ikke parret enheder" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Discovery Deaktiveret" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Visningstilstand" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Bruger Menu" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Generere Underbygge Log" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Om GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Vælg en mobil enhed" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Vælg" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Ingen Enhed Fundet" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Enhedsliste" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Anmeld" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "" #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Tekniske Detaljer" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Send til Mobil Enhed" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d Tilsuttet" msgstr[1] "%d Tilsuttet" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Rediger" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Fjerne" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Deaktiveret" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Indtast en ny genvej for at ændre %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s er allerede bliver brugt" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "En komplet KDE Connect implementering for GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "oversætter-kreditter" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Fejlmeddelelser er logges. Tage de nødvendige skridt til at reproducere et problem, og gennemgÃ¥ loggen." #: src/preferences/service.js:421 msgid "Review Log" msgstr "GennemgÃ¥ Loggen" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Bærbar" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Fjernsyn" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Ikke parret" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Frakoblet" #: src/preferences/service.js:525 msgid "Connected" msgstr "Tilsluttet" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Venter pÃ¥ service…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Klik for hjælp fejlfinding" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Klik for mere information" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Opkaldsnummer" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Del Fil" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Liste over tilgængelige enheder" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Liste alle enheder" #: src/service/daemon.js:373 msgid "Target Device" msgstr "MÃ¥let Enhed" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Beskedkrop" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Send Notifikation" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Notifikation Ikon" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Notifikation ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Ring" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Del Link" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Del tekst" #: src/service/daemon.js:532 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth enhed pÃ¥ %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Paranmodning fra %s" #: src/service/device.js:850 msgid "Reject" msgstr "Afvise" #: src/service/device.js:855 msgid "Accept" msgstr "Acceptere" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Opdagelse er deaktiveret pÃ¥ grund af antallet af enheder pÃ¥ dette netværk." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL ikke fundet" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Batteriet er fyldt" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Fuldt Opladet" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Batteriet er lavt" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% tilbage" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Udklipsholder" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Udklipsholder Tryk" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Udklipsholder Trække" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Find min telefon" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "MusemÃ¥tte" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Ukendt" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Annullere Besked" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Luk Besked" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Svar Notifikation" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Aktiver Notifikation" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Overførsel mislykkedes" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Kunne ikke send \"%s\" til %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Præsentation" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Udføre Kommando" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Monter" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Afmonter" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Del" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s er ikke tilladt at uploade filer" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Overfører Fil" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Modtager \"%s\" fra %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Overførsel Succesfuld" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Modtaget \"%s\" fra %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Ã…ben fil" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Kunne ikke modtage \"%s\" fra %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Tekst Deles Af %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Sende \"%s\" til %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Sendt \"%s\" til %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Send filer til %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Ã…bne, nÃ¥r færdig" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Send et link til %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Ny SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Besvar SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Del SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Systemlydstyrke" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio ikke fundet" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Stum Opkald" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Ukendt Kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "IndgÃ¥ende Opkald" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Igangværende Opkald" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Arbejde" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Hjem" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Send til %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Lige Nu" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "I gÃ¥r・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minutter" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Ikke Tilgængelig" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Gruppebesked" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Du: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Og %d andre kontakt" msgstr[1] "Og %d andre" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Estimering…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d:%02d Indtil Fuld)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d Resterende)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Svar" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Dele links med GSConnect, direkte til browseren eller via SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Tjenesten er ikke tilgængelig" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Ã…bn i browser" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/de.po000066400000000000000000001066511460766671100234230ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-08-25 22:45\n" "Last-Translator: \n" "Language-Team: German\n" "Language: de_DE\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: de\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "KDE-Connect-Implementierung für GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect-Team" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect ist eine vollständige Implementierung von KDE Connect für GNOME Shell, insbesondere mit Nautilus-, Chrome- und Firefox-Integration. Das KDE-Connect-Team stellt für Linux, BSD, Android, Sailfish, iOS, macOS und Windows Anwendungen bereit." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Mit GSConnect können Sie mobile Geräte und andere Desktops sicher verbinden mit:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Dateien, Links und Text teilen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Nachrichten senden und abrufen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Inhalt der Zwischenablage abgleichen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Kontakte synchronisieren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Benachrichtigungen synchronisieren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Medienwiedergaben steuern" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Systemlautstärke steuern" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Vordefinierte Befehle ausführen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Und mehr …" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect in der GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Verbinden mit …" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Abbruch" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Verbinden" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP-Adresse" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Keine Kontakte" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Hilfe" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Telefonnummer oder Name eingeben" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Andere" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "SMS senden" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Senden" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Gerät ist getrennt" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Nachricht senden" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Eine Nachricht verfassen" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Nachrichteneintrag" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Tippen Sie eine Nachricht, zum Senden die Eingabetaste drücken" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Nachrichten" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Neue Unterhaltung" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Keine Unterhaltungen" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Keine Unterhaltung ausgewählt" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Eine Unterhaltung auswählen oder starten" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Tastfeld.\n" "Streichen Sie über diesen Bereich, um den Mauszeiger zu bewegen.\n" "Wenn Sie länger drücken, können Sie Objekte mit dem Mauszeiger bewegen.\n\n" "Einfaches Klicken wird an das gekoppelte Gerät gesendet.\n" "Links-, Mittel-, Rechtsklick und Radrollen." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Befehl bearbeiten" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Speichern" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Name" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Befehlszeile" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Ein ausführbares Programm auswählen" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Öffnen" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Schreibtisch" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Zwischenablagesynchronisierung" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Medienwiedergaben" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Maus & Tastatur" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Lautstärkeregler" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Dateien" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Dateien empfangen" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Dateien speichern unter" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Teilen" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Geräteakku" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Benachrichtigung bei geringem Akkustand" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "»Benutzerdefinierter Ladestand erreicht«-Benachrichtigung" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "»Vollständig geladen«-Benachrichtigung" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Systemakku" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Statistik teilen" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Akku" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Befehle" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Befehl hinzufügen" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Benachrichtigungen freigeben" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Teilen wenn aktiv" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Anwendungen" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Benachrichtigungen" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontakte" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Eingehende Anrufe" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Lautstärke" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Medienwiedergabe pausieren" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Laufende Anrufe" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Mikrofon stummschalten" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonie" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Aktionstastenkürzel" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Alles zurücksetzen …" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Tastenkürzel" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Module" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimentell" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Gerätezwischenspeicher" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Zwischenspeicher leeren …" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Alte SMS-Unterstützung" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Automatisches SFTP-Einhängen" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Erweitert" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Tastenkürzel" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Geräteeinstellungen" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Koppeln" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Gerät ist nicht gekoppelt" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Sie können dieses Gerät vor dem Koppeln konfigurieren" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Verschlüsselungsinfo" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Entkoppeln" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Zum Gerät" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Vom Gerät" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Nichts" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Wiederherstellen" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Leiser" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Stummschalten" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Festlegen" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Zum Abbrechen ESC oder die Rücktaste drücken, um das Tastenkürzel zurückzusetzen." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Gerätename" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Umbenennen" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Auffrischen" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobile Einstellungen" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Dienstmenü" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Gerätemenü" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Gerätenamen bearbeiten" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Geräte" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Geräte werden gesucht…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Erweiterungseinstellungen" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect bleibt aktiv, wenn GNOME Shell gesperrt ist" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Browser-Erweiterungen" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Aktivieren" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Dieses Gerät ist für nicht gekoppelte Geräte unsichtbar" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Erkennen deaktiviert" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Anzeigemodus" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Leiste" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Benutzermenü" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Hilfeprotokoll generieren" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Info zu GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Gerät auswählen" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Auswählen" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Kein Gerät gefunden" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Geräteliste" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Bericht" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Etwas ist schiefgelaufen" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect hatte ein unerwartetes Problem. Bitte melden Sie den Fehler und stellen Sie nützliche Informationen bereit." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Technische Details" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "An Mobilgerät senden" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Geräte synchronisieren" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d verbunden" msgstr[1] "%d verbunden" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Bearbeiten" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Entfernen" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Deaktiviert" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Ein neues Tastenkürzel eingeben, um %s zu ändern" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s wird bereits verwendet" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Eine vollständige KDE-Connect-Implementierung für GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "taaem \n" "Tobias Bannert \n" "Björn Daase (BjoernDaase)" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Debug-Meldungen werden protokolliert. Führen Sie alle erforderlichen Schritte aus, um ein Problem zu reproduzieren und überprüfen Sie dann das Protokoll." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Protokoll überprüfen" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Fernseher" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Ungekoppelt" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Getrennt" #: src/preferences/service.js:525 msgid "Connected" msgstr "Verbunden" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Auf Dienst wird gewartet …" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Für Hilfe bei der Fehlerbehebung hier klicken" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Für mehr Informationen klicken" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Nummer wählen" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Datei freigeben" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Alle verfügbaren Geräte anzeigen" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Alle Geräte anzeigen" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Zielgerät" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Nachrichtentext" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Benachrichtigung senden" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Name der Benachrichtigungs-App" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Benachrichtigungsinhalt" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Benachrichtigungssymbol" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Benachrichtigungs-ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Klingeln" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Link freigeben" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Text teilen" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Neuste Version zeigen" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-Gerät bei %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Bestätigungsschlüssel: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Kopplung von %s angefordert" #: src/service/device.js:850 msgid "Reject" msgstr "Ablehnen" #: src/service/device.js:855 msgid "Accept" msgstr "Annehmen" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Erkennen wurde aufgrund der Anzahl an Geräten in diesem Netzwerk deaktiviert." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL nicht gefunden" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Port wird bereits verwendet" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Akku-Informationen austauschen" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Akku ist voll" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Vollständig geladen" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Akku hat den benutzerdefinierten Ladezustand erreicht" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% geladen" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Akku ist leer" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% verbleibend" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Zwischenablage" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Inhalt der Zwischenablage freigeben" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Zwischenablage senden" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Zwischenablage laden" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Auf Kontakte des verbundenen Gerätes zugreifen" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Mein Handy finden" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Das verbundene Gerät klingeln lassen" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Mauspad" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Das verbundene Gerät als entfernte Maus und Tastatur verwenden" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Eingaben aus der Ferne" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Bidirektionale Steuerung der Medienwiedergabe" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Unbekannt" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Benachrichtigungen für das verbundene Gerät freigeben" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Benachrichtigung abbrechen" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Benachrichtigung schließen" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Auf Benachrichtigung antworten" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Benachrichtigung aktivieren" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Das verbundene Gerät anweisen, ein Foto schießen und es an diesen PC zu übertragen" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Übertragung gescheitert" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Senden von »%s« an %s gescheitert" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Pings senden und empfangen" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Präsentation" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Das verbundene Gerät als Präsentations-Fernbedienung nutzen" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Befehle ausführen" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Befehle auf dem verbundenen Gerät ausführen oder das Gerät vordefinierte Befehle auf diesem Rechner ausführen lassen" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Das Dateisystem des verbundenen Gerätes durchsuchen" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Einhängen" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Aushängen" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s meldete einen Fehler" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Teilen" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Dateien und URLs zwischen Geräten freigeben" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s besitzt keine Berechtigung, Dateien hochzuladen" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Datei wird übertragen" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "»%s« wird von %s empfangen" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Übertragung erfolgreich" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "»%s« von %s empfangen" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Speicherort der Datei anzeigen" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Datei öffnen" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Empfang von »%s« von %s gescheitert" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Text von %s freigegeben" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "»%s« wird an %s gesendet" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "»%s« an %s gesendet" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Dateien an %s senden" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Öffnen wenn abgeschlossen" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Verweis an %s senden" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "SMS über das verbundene Gerät senden/empfangen und über neue SMS benachrichtigt werden" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Neue SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Auf SMS antworten" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "SMS freigeben" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Systemlautstärke" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Dem verbundenen Gerät die Steuerung der Systemlautstärke erlauben" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio nicht gefunden" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Über Anrufe benachrichtigt werden und Systemlautstärke während klingelnden/laufenden Anrufen anpassen" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Anruf stummschalten" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Unbekannter Kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Eingehender Anruf" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Laufender Anruf" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Arbeit" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Zuhause" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "An %s senden" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Gerade jetzt" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Gestern・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d Minute" msgstr[1] "%d Minuten" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Nicht verfügbar" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Gruppennachricht" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Sie: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Und %d anderer Kontakt" msgstr[1] "Und %d andere" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Tastaturfernsteuerung auf %s ist nicht aktiv" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (wird geschätzt …)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d:%02d bis vollständig aufgeladen)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d verbleibend)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Antworten" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Verweise mit GSConnect direkt an den Browser oder per SMS freigeben." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Dienst nicht verfügbar" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Im Browser öffnen" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/el.po000066400000000000000000001223421460766671100234260ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-01 04:14+0300\n" "PO-Revision-Date: 2024-04-01 04:16+0300\n" "Last-Translator: \n" "Language-Team: \n" "Language: el\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: Poedit 3.4.2\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:30 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "ΕφαÏμογή του KDE Connect για το GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Η ομάδα του GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "" "GSConnect is a complete implementation of KDE Connect especially for GNOME " "Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team " "has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" "Το GSConnect είναι μια πλήÏης υλοποίηση του KDE Connect για το γÏαφικό " "κέλυφος GNOME με ενσωμάτωση Nautilus, Chrome και Firefox. Η ομάδα του KDE " "Connect διαθέτει εφαÏμογές για Linux, BSD, Android, Sailfish, macOS και " "Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "" "With GSConnect you can securely connect to mobile devices and other desktops " "to:" msgstr "" "Με το GSConnect μποÏείτε να συνδεθείτε με ασφάλεια σε κινητές συσκευές και " "άλλους επιτÏαπέζιους υπολογιστές για να:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "ΜοιÏαστείτε αÏχεία, συνδέσμους και κείμενο" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Στείλτε και λάβετε μηνÏματα" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "ΣυγχÏονίσετε το πεÏιεχόμενο του Ï€ÏόχειÏου σας" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "ΣυγχÏονίσετε της επαφές σας" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "ΣυγχÏονίσετε της ειδοποίησης σας" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Ελέγξετε της συσκευές σας αναπαÏαγωγής πολυμέσων" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Ελέξετε την ένταση του συστήματος σας" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Εκτελέστε Ï€ÏοκαθοÏισμένες εντολές" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Και άλλα…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:133 msgid "GSConnect in GNOME Shell" msgstr "GSConnect στο γÏαφικό κέλυφος GNOME" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Συνδεθείτε στο…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:405 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "ΑκÏÏωση" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "ΣÏνδεση" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "ΔιεÏθυνση IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Καμία επαφή" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Βοήθεια" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "ΠληκτÏολογήστε έναν αÏιθμό τηλεφώνου ή ένα όνομα" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Άλλα" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:293 #: src/service/daemon.js:407 src/service/plugins/sms.js:64 #: webextension/gettext.js:42 msgid "Send SMS" msgstr "Στείλτε SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Στείλτε" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Η συσκευή είναι αποσυνδεδεμένη" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Στείλτε μήνυμα" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 msgid "Type a message" msgstr "ΠληκτÏολογήστε ένα μήνυμα" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "ΚαταχώÏηση μηνÏματος" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "ΠληκτÏολογήστε ένα μήνυμα και πατήστε Enter για αποστολή" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Αποστολή μηνυμάτων" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 msgid "New Conversation" msgstr "Îέα συζήτηση" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Δεν υπάÏχουν συνομιλίες" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Δεν έχει επιλεγεί συνομιλία" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Επιλέξτε ή ξεκινήστε μια συζήτηση" #: data/ui/mousepad-input-dialog.ui:97 msgid "" "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n" "\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" "Επιφάνεια αφής.\n" "ΣÏÏετε σε αυτή την πεÏιοχή για να μετακινήσετε τον κέÏσοÏα του ποντικιοÏ.\n" "Πατήστε παÏατεταμένα για να σÏÏετε τον κέÏσοÏα του ποντικιοÏ.\n" "\n" "Απλά πατήστε και το πλήκτÏο θα αποσταλεί στη συνδεδεμένη συσκευή.\n" "ΑÏιστεÏÏŒ, μεσαίο, δεξί κουμπί και Ï„Ïοχός κÏλισης." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Εντολή επεξεÏγασίας" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Αποθήκευση" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Όνομα" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "ΓÏαμμή εντολών" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Επιλέξτε ένα εκτελέσιμο αÏχείο" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Ανοίξτε" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:482 msgid "Desktop" msgstr "Επιφάνεια εÏγασίας" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "ΣυγχÏονισμός Ï€ÏόχειÏου" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Συσκευές αναπαÏαγωγής πολυμέσων" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "ΠληκτÏολόγιο και ποντίκι" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Έλεγχος έντασης ήχου" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:334 msgid "Files" msgstr "ΑÏχεία" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Λήψη αÏχείων" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Αποθήκευση αÏχείων σε" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Κοινή χÏήση" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "ΜπαταÏία συσκευής" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Ειδοποίηση χαμηλής στάθμης μπαταÏίας" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Ειδοποίηση σε Ï€ÏοσαÏμοσμένο επίπεδο ΦόÏτιση" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Ειδοποίηση πλήÏους φόÏτισης" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "ΜπαταÏία συστήματος" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "ΜοιÏαστείτε τα στατιστικά στοιχεία σας" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:16 msgid "Battery" msgstr "ΜπαταÏία" #: data/ui/preferences-device-panel.ui:916 #: data/ui/preferences-device-panel.ui:1001 #: data/ui/preferences-device-panel.ui:2262 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Εντολές" #: data/ui/preferences-device-panel.ui:975 msgid "Add Command" msgstr "ΠÏοσθήκη εντολής" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1063 msgid "Share Notifications" msgstr "Κοινοποίηση ειδοποιήσεων" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "ΜοιÏαστείτε όταν είστε ενεÏγός" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "ΕφαÏμογές" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Ειδοποιήσεις" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Επαφές" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "ΕισεÏχόμενες κλήσεις" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Ένταση" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "ΠαÏση Πολυμέσων" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Συνεχείς κλήσεις" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Σίγαση μικÏοφώνου" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Τηλεφωνία" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "ΣυντομεÏσεις ενεÏγειών" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "ΕπαναφοÏά όλων…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "ΣυντομεÏσεις" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "ΠÏόσθετα" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "ΠειÏαματικά" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Μνήμη cache συσκευής" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "ΕκκαθάÏιση μνήμης cache…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "ΥποστήÏιξη SMS Legacy" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Αυτόματη Ï€ÏοσάÏτηση SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "ΠÏοηγμένα" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "ΣυντομεÏσεις πληκτÏολογίου" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Ρυθμίσεις συσκευής" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2508 #: data/ui/preferences-device-panel.ui:2600 src/service/daemon.js:386 msgid "Pair" msgstr "ΣÏζευξή" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Η συσκευή δεν είναι συζευγμένη" #: data/ui/preferences-device-panel.ui:2555 msgid "You may configure this device before pairing" msgstr "" "ΜποÏείτε να Ïυθμίσετε τις παÏαμέτÏους αυτής της συσκευής Ï€Ïιν από την σÏζευξή" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2595 src/preferences/device.js:391 msgid "Encryption Info" msgstr "ΠληÏοφοÏίες κÏυπτογÏάφησης" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:395 msgid "Unpair" msgstr "ΑποσÏζευξή" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "ΠÏος τη συσκευή" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Από τη συσκευή" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Τίποτα" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "ΕπαναφοÏά" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Χαμηλώστε" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2657 #: data/ui/preferences-device-panel.ui:2690 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Σίγαση" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "ΟÏίστε" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" "Πατήστε Esc για ακÏÏωση ή Backspace για επαναφοÏά της συντόμευσης " "πληκτÏολογίου." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Όνομα συσκευής" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Μετονομασία" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Ανανέωση" #: data/ui/preferences-window.ui:139 src/extension.js:114 msgid "Mobile Settings" msgstr "Ρυθμίσεις κινητοÏ" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "ÎœÎµÎ½Î¿Ï Ï…Ï€Î·Ïεσιών" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "ÎœÎµÎ½Î¿Ï ÏƒÏ…ÏƒÎºÎµÏ…Î®Ï‚" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "ΕπεξεÏγασία ονόματος συσκευής" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Συσκευές" #: data/ui/preferences-window.ui:328 src/preferences/service.js:644 msgid "Searching for devices…" msgstr "Αναζήτηση συσκευών…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Ρυθμίσεις επέκτασης" #: data/ui/preferences-window.ui:390 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "Το GSConnect παÏαμένει ενεÏγό όταν το κέλυφος GNOME είναι κλειδωμένο" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "ΠÏόσθετα Ï€ÏογÏάμματος πεÏιήγησης" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "ΕνεÏγοποίηση" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Αυτή η συσκευή είναι αόÏατη σε μη συζευγμένες συσκευές" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Η ανακάλυψη είναι απενεÏγοποιημένη" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "ΛειτουÏγία Ï€Ïοβολής" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Πάνελ" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "ÎœÎµÎ½Î¿Ï Ï‡Ïήστη" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:402 msgid "Generate Support Log" msgstr "ΔημιουÏγία αÏχείου υποστήÏιξης" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Σχετικά με το GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Επιλέξτε μια συσκευή" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Επιλέξτε" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:38 msgid "No Device Found" msgstr "Δεν βÏέθηκε συσκευή" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Λίστα συσκευών" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "ΑναφοÏά" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Κάτι πήγε στÏαβά" #: data/ui/service-error-dialog.ui:91 msgid "" "GSConnect encountered an unexpected error. Please report the problem and " "include any information that may help." msgstr "" "Το GSConnect αντιμετώπισε ένα απÏοσδόκητο σφάλμα. ΑναφέÏετε το Ï€Ïόβλημα και " "συμπεÏιλάβετε οποιαδήποτε πληÏοφοÏία μποÏεί να βοηθήσει." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Τεχνικές πληÏοφοÏίες" #. TRANSLATORS: Send to , for file manager #. context menu #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: nautilus-extension/nautilus-gsconnect.py:187 src/service/ui/contacts.js:509 #: src/service/ui/contacts.js:524 #, python-format, javascript-format msgid "Send to %s" msgstr "Αποστολή σε %s" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:195 webextension/gettext.js:34 msgid "Send To Mobile Device" msgstr "Αποστολή σε φοÏητή συσκευή" #: src/extension.js:50 msgid "Sync between your devices" msgstr "ΣυγχÏονισμός Î¼ÎµÏ„Î±Î¾Ï Ï„Ï‰Î½ συσκευών σας" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d Συνδεδεμένη" msgstr[1] "%d Συνδεδεμένες" #: src/preferences/device.js:670 src/preferences/device.js:676 msgid "Edit" msgstr "ΕπεξεÏγασία" #: src/preferences/device.js:685 src/preferences/device.js:691 msgid "Remove" msgstr "ΑφαίÏεση" #: src/preferences/device.js:945 src/preferences/device.js:973 msgid "Disabled" msgstr "Ανεσταλμένες" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Εισάγετε μια νέα συντόμευση για να αλλάξετε το %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "Το %s χÏησιμοποιείται ήδη" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Μια πλήÏης υλοποίηση του KDE Connect για το GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "" "Αθανάσιος-ÎεκτάÏιος ΚαÏαχάλιος-Στάγκας " #: src/preferences/service.js:403 msgid "" "Debug messages are being logged. Take any steps necessary to reproduce a " "problem then review the log." msgstr "" "Τα μηνÏματα ÎµÎ½Ï„Î¿Ï€Î¹ÏƒÎ¼Î¿Ï ÏƒÏ†Î±Î»Î¼Î¬Ï„Ï‰Î½ καταγÏάφονται. ΠάÏτε τα απαÏαίτητα μέτÏα " "για την αναπαÏαγωγή ενός Ï€Ïοβλήματος και στη συνέχεια επανεξετάστε το αÏχείο " "καταγÏαφής." #: src/preferences/service.js:406 msgid "Review Log" msgstr "Επισκόπηση αÏχείων καταγÏαφής" #: src/preferences/service.js:474 msgid "Laptop" msgstr "ΦοÏητός υπολογιστής" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Κινητό" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "ΤηλεόÏαση" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Μη συζευγμένο" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Αποσυνδεδεμένο" #: src/preferences/service.js:510 msgid "Connected" msgstr "Συνδεδεμένο" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Αναμονή για την υπηÏεσία…" #: src/service/daemon.js:194 msgid "Click for help troubleshooting" msgstr "Κάντε κλικ για την βοήθεια αντιμετώπισης Ï€Ïοβλημάτων" #: src/service/daemon.js:205 msgid "Click for more information" msgstr "Κάντε κλικ για πεÏισσότεÏες πληÏοφοÏίες" #: src/service/daemon.js:299 msgid "Dial Number" msgstr "ΠληκτÏολογήστε τον αÏιθμό" #: src/service/daemon.js:305 src/service/daemon.js:494 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Κοινή χÏήση αÏχείου" #: src/service/daemon.js:356 msgid "List available devices" msgstr "Λίστα διαθέσιμων συσκευών" #: src/service/daemon.js:365 msgid "List all devices" msgstr "Λίστα όλων των συσκευών" #: src/service/daemon.js:374 msgid "Target Device" msgstr "Συσκευή Ï€ÏοοÏισμοÏ" #: src/service/daemon.js:416 msgid "Message Body" msgstr "ΚοÏμός μηνÏματος" #: src/service/daemon.js:428 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Αποστολή ειδοποίησης" #: src/service/daemon.js:437 msgid "Notification App Name" msgstr "Ονομασία εφαÏμογής ειδοποίησης" #: src/service/daemon.js:446 msgid "Notification Body" msgstr "ΚοÏμός ειδοποίησης" #: src/service/daemon.js:455 msgid "Notification Icon" msgstr "Εικονίδιο ειδοποίησης" #: src/service/daemon.js:464 msgid "Notification ID" msgstr "ΑναγνωÏιστικό ειδοποίησης" #: src/service/daemon.js:473 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:482 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Î’Ïες" #: src/service/daemon.js:503 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "ΔιαμοιÏασμός συνδέσμου" #: src/service/daemon.js:512 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Αποστολή κειμένου" #: src/service/daemon.js:524 msgid "Show release version" msgstr "Εμφάνιση έκδοσης έκδοσης" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Συσκευή Bluetooth σε %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Κλειδί επαλήθευσης: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:848 #, javascript-format msgid "Pair Request from %s" msgstr "Αίτημα ζεÏγους από %s" #: src/service/device.js:855 msgid "Reject" msgstr "ΑπόÏÏιψη" #: src/service/device.js:860 msgid "Accept" msgstr "Αποδοχή" #: src/service/manager.js:118 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" "Η ανίχνευση έχει απενεÏγοποιηθεί λόγω του αÏÎ¹Î¸Î¼Î¿Ï Ï„Ï‰Î½ συσκευών σε αυτό το " "δίκτυο." #: src/service/backends/lan.js:169 msgid "OpenSSL not found" msgstr "Το OpenSSL δεν βÏέθηκε" #: src/service/backends/lan.js:462 msgid "Port already in use" msgstr "Η θÏÏα χÏησιμοποιείται ήδη" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Ανταλλαγή πληÏοφοÏιών μπαταÏίας" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Η μπαταÏία είναι πλήÏης" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:119 msgid "Fully Charged" msgstr "ΠλήÏης φόÏτιση" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Η μπαταÏία έχει φτάσει σε Ï€ÏοσαÏμοσμένο επίπεδο φόÏτισης" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% ΦοÏτίστηκε" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Η μπαταÏία είναι χαμηλή" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% απομένει" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "ΠÏόχειÏο" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "ΜοιÏαστείτε το πεÏιεχόμενο του Ï€ÏόχειÏου" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Αποστόλη Ï€ÏόχειÏου" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Λήψη Ï€ÏόχειÏου" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "ΠÏόσβαση στις επαφές της συζευγμένης συσκευής" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "ΕÏÏεση τηλεφώνου" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Καλέστε τη συζευγμένη συσκευή σας" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" "ΕπιτÏέπει στη συζευγμένη συσκευή να λειτουÏγεί ως απομακÏυσμένο ποντίκι και " "πληκτÏολόγιο" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "ΑπομακÏυσμένη εισαγωγή" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "ΑμφίδÏομος απομακÏυσμένος έλεγχος αναπαÏαγωγής πολυμέσων" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Άγνωστο" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Κοινή χÏήση ειδοποιήσεων με τη συζευγμένη συσκευή" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "ΑκÏÏωση ειδοποίησης" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Κλείσιμο ειδοποίησης" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Απάντηση ειδοποίησης" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "ΕνεÏγοποίηση ειδοποίησης" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Αποστολή και λήψη pings" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "ΠαÏουσίαση" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "ΧÏήση της συζευγμένης συσκευής ως παÏουσιαστή" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Εντολές εκτέλεσης" #: src/service/plugins/runcommand.js:17 msgid "" "Run commands on your paired device or let the device run predefined commands " "on this PC" msgstr "" "Εκτελέστε εντολές στη συζευγμένη συσκευή σας ή αφήστε τη συσκευή να " "εκτελέσει Ï€ÏοκαθοÏισμένες εντολές σε αυτόν τον υπολογιστή" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "ΠεÏιηγηθείτε στο σÏστημα αÏχείων της συζευγμένης συσκευής" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "ΠÏοσάÏτηση" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "ΑποπÏοσάÏτηση" #: src/service/plugins/sftp.js:193 #, javascript-format msgid "%s reported an error" msgstr "%s ανέφεÏε ένα σφάλμα" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Κοινοποίηση" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Κοινή χÏήση αÏχείων και διευθÏνσεων URL Î¼ÎµÏ„Î±Î¾Ï ÏƒÏ…ÏƒÎºÎµÏ…ÏŽÎ½" #: src/service/plugins/share.js:132 src/service/plugins/share.js:208 #: src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Η μεταφοÏά απέτυχε" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s δεν επιτÏέπεται το ανέβασμα αÏχείων" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "ΜεταφοÏά αÏχείου" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Λήψη \"%s\" από %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Επιτυχής μεταφοÏά" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Ελήφθη \"%s\" από %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Εμφάνιση θέσης αÏχείου" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Άνοιγμα αÏχείου" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Απέτυχε η λήψη του \"%s\" από το %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Κείμενο κοινοποιημένο από %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Αποστολή \"%s\" σε %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Εστάλη το \"%s\" στο %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Απέτυχε η αποστολή του \"%s\" στο %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Αποστολή αÏχείων στο %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Άνοιγμα όταν τελειώσει" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Στείλτε έναν σÏνδεσμο στο %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" "Αποστολή και ανάγνωση SMS της συζευγμένης συσκευής και ειδοποίηση για νέα SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Îέο SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Απάντηση SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Κοινοποίηση SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Ένταση συστήματος" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" "ΕνεÏγοποίηση της συζευγμένης συσκευής για τον έλεγχο της έντασης ήχου του " "συστήματος" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "Δεν βÏέθηκε το PulseAudio" #: src/service/plugins/telephony.js:18 msgid "" "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" "Ειδοποιηθείτε για κλήσεις και Ïυθμίστε την ένταση του συστήματος κατά τη " "διάÏκεια/εν εξελίξει κλήσεων" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Σίγαση κλήσης" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Άγνωστη επαφή" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "ΕισεÏχόμενη κλήση" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Εν εξέλιξη κλήση" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Φαξ" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Δουλειά" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Κινητό" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Σπίτι" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Μόλις τώÏα" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Χθες・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d λεπτό" msgstr[1] "%d λεπτά" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Δεν είναι διαθέσιμο" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Ομαδικό μήνυμα" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Εσείς: %s" #: src/service/ui/messaging.js:959 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Και %d άλλη επαφή" msgstr[1] "Και %d άλλοι" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Το απομακÏυσμένο πληκτÏολόγιο στο %s δεν είναι ενεÏγό" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:124 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Υπολογισμός...)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:133 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d ΜέχÏι να γεμίσει)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d Υπόλοιπο)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Απάντηση" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "" "ΜοιÏαστείτε συνδέσμους με το GSConnect, απευθείας στο Ï€ÏόγÏαμμα πεÏιήγησης ή " "μέσω SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:36 msgid "Service Unavailable" msgstr "Η υπηÏεσία δεν είναι διαθέσιμη" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Άνοιγμα στο Ï€ÏόγÏαμμα πεÏιήγησης" #~ msgid "Camera" #~ msgstr "ΚάμεÏα" #~ msgid "Behavior When Locked" #~ msgstr "ΣυμπεÏιφοÏά όταν είναι κλειδωμένο" #~ msgid "Keep Alive" #~ msgstr "ΔιατηÏήστε" #~ msgid "Photo" #~ msgstr "ΦωτογÏαφία" #~ msgid "Request the paired device to take a photo and transfer it to this PC" #~ msgstr "" #~ "Ζητήστε από τη συζευγμένη συσκευή να Ï„Ïαβήξει μια φωτογÏαφία και να τη " #~ "μεταφέÏει σε αυτόν τον υπολογιστή" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/es.po000066400000000000000000001071751460766671100234440ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-02 05:59\n" "Last-Translator: \n" "Language-Team: Spanish\n" "Language: es_ES\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: es-ES\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Una implementación de KDE Connect para GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Equipo de GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect es una implementación completa de KDE Connect especialmente para GNOME Shell con integración para Nautilus, Chrome y Firefox. El equipo de KDE Connect tiene aplicaciones para Linux, BSD, Android, Sailfish, iOS, macOS y Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Con GSConnect, puede conectar de manera segura con dispositivos móviles y de escritorio para:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Compartir archivos, enlaces y texto" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Enviar y recibir mensajes" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Sincronizar el contenido del portapapeles" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Sincronizar los contactos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Sincronizar las notificaciones" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Controlar reproductores multimedia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Controlar el volumen del sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Ejecutar órdenes predefinidas" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Y más…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect en GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Conectar a…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Cancelar" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Conectar" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Dirección IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "No hay ningún contacto" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Ayuda" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Escriba un número telefónico o un nombre" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Otro" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Enviar SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Enviar" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "El dispositivo está desconectado" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Enviar mensaje" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Escriba un mensaje" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Entrada de mensaje" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Escriba un mensaje y presione Enter para enviar" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Mensajería" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Conversación nueva" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "No hay ninguna conversación" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "No se seleccionó ninguna conversación" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Seleccione una conversación o inicie una" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Panel táctil.\n" "Arrastre en esta zona para mover el cursor del ratón.\n" "Pulse prolongadamente para arrastrar el cursor del ratón.\n\n" "Un simple clic se enviará al dispositivo emparejado.\n" "Botón izquierdo, central, derecho y rueda de desplazamiento." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Editar orden" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Guardar" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nombre" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Línea de órdenes" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Elija un ejecutable" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Abrir" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Equipo de escritorio" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Cámara" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Sincronización de portapapeles" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Reproductores multimedia" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Ratón y teclado" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Control de volumen" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Archivos" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Recibir archivos" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Guardar archivos en" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Compartición" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Batería del dispositivo" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Notificación de batería baja" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Notificación de nivel de carga personalizado" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Notificación de carga completa" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Batería del sistema" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Compartir estadísticas" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Batería" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Órdenes" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Añadir orden" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Notificaciones de compartición" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Compartir mientras haya actividad" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Aplicaciones" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notificaciones" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contactos" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Llamadas entrantes" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volumen" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Pausar multimedia" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Llamadas en curso" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Silenciar micrófono" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonía" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Atajos de acciones" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Restablecer todo…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Atajos" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Complementos" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimentos" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Antememoria de dispositivo" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Vaciar antememoria…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Compatibilidad SMS heredada" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Montaje automático SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avanzado" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Atajos de teclado" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Configuración del dispositivo" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Emparejamiento" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "El dispositivo no está emparejado" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Puede configurar este dispositivo antes de emparejarlo" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Información de cifrado" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Desemparejar" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Al dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Del dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Nada" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Disminuir" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Silenciar" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Establecer" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Oprima Esc para cancelar o Retroceso para restablecer el atajo de teclado." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nombre del dispositivo" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Cambiar nombre" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Actualizar" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Configuración de móvil" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menú de servicios" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menú de dispositivos" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Editar nombre de dispositivo" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Dispositivos" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Buscando dispositivos…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Configuración de la extensión" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect permanece activo mientras GNOME Shell está bloqueado" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Complementos para navegadores" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Activar" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Este dispositivo es invisible a dispositivos no emparejados" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Descubrimiento desactivado" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Modo de visualización" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Menú de usuario" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Generar registro para asistencia" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Acerca de GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Seleccione un dispositivo" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Seleccionar" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "No se encontró ningún dispositivo" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Lista de dispositivos" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Informe" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Algo ha salido mal" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect encontró un error inesperado. Por favor, informe del problema e incluya cualquier información que pueda ayudar." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Detalles técnicos" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Enviar a dispositivo móvil" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Sincronizar entre sus dispositivos" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d conectado" msgstr[1] "%d conectados" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Editar" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Quitar" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Desactivado" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Digite un atajo nuevo para cambiar %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "Ya está utilizándose %s" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Una completa implementación de KDE Connect para GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Adolfo Jayme Barrientos , 2018-2019" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Los mensajes de depuración están registrándose. Realice las acciones necesarias para reproducir un problema y, a continuación, revise el registro." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Revisar registro" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Equipo portátil" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Teléfono inteligente" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tableta" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisión" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Desemparejado" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Desconectado" #: src/preferences/service.js:525 msgid "Connected" msgstr "Conectado" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Esperando el servicio…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Pulse para obtener información de solución de problemas" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Pulse para más información" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Marcar número" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Compartir archivo" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Enumerar dispositivos disponibles" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Enumerar todos los dispositivos" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Dispositivo de destino" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Cuerpo del mensaje" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Enviar notificación" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Nombre de la aplicación de la notificación" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Cuerpo de la notificación" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Icono de la notificación" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Identificador de la notificación" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Fotografía" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Prueba de conectividad" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Timbrar" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Compartir enlace" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Compartir texto" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Mostrar versión" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositivo Bluetooth en %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Clave de verificación: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Solicitud de emparejamiento de %s" #: src/service/device.js:850 msgid "Reject" msgstr "Rechazar" #: src/service/device.js:855 msgid "Accept" msgstr "Aceptar" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Se desactivó el descubrimiento debido al número de dispositivos presentes en esta red." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "No se encontró OpenSSL" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Puerto ya en uso" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Intercambiar información sobre la batería" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: batería cargada" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Carga completa" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: batería cargada al nivel personalizado" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d %% cargada" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batería baja" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d %% restante" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Portapapeles" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Compartir el contenido del portapapeles" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Envío a portapapeles" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Recepción desde portapapeles" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Acceder a los contactos del dispositivo emparejado" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Encontrar mi teléfono" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Hacer sonar su dispositivo emparejado" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Permite que el dispositivo emparejado actúe como ratón y teclado remotos" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Entrada remota" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Control remoto de reproducción multimedia bidireccional" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Desconocido" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Compartir notificaciones con el dispositivo emparejado" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Cancelar notificación" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Cerrar notificación" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Notificación de respuesta" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Activar notificación" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Solicitar al dispositivo emparejado que tome una foto y la transfiera a este equipo" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Transferencia fallida" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Falló el envío de «%s» a %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Enviar y recibir pings" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Prueba de conectividad: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Presentación" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Utilizar el dispositivo emparejado como presentador" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Ejecutar órdenes" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Ejecute órdenes en su dispositivo emparejado o deje que el dispositivo ejecute órdenes predefinidas en este equipo" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Examinar el sistema de archivos del dispositivo emparejado" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s informó de un error" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Compartición" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Compartir archivos y URLs entre dispositivos" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s no tiene permitido cargar archivos" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Transfiriendo archivo" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Recibiendo «%s» de %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Transferencia exitosa" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Se recibió «%s» de %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Mostrar ubicación del archivo" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Abrir archivo" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Falló la recepción de «%s» desde %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Texto compartido por %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Enviando «%s» a %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Se envió «%s» a %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Enviar archivos a %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Abrir al terminar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Enviar un enlace a %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Enviar y leer SMS del dispositivo emparejado y recibir notificaciones de nuevos SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "SMS nuevo (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Responder a SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Compartir SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volumen del sistema" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Habilitar el dispositivo emparejado para controlar el volumen del sistema" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "No se encontró PulseAudio" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Ser notificado sobre las llamadas y ajustar el volumen del sistema durante las llamadas que suenan/están en curso" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silenciar llamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Contacto desconocido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Llamada entrante" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Llamada en curso" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Trabajo" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Móvil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Residencial" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Enviar a %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Ahora mismo" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Ayer・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "No disponible" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Mensaje grupal" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Usted: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Y %d contacto más" msgstr[1] "Y %d más" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "El teclado remoto de %s no está activo" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d %% (estimando…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d %% (%d∶%02d hasta completarse)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d %% (quedan %d∶%02d)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Comparta enlaces con GSConnect, directamente al navegador o a través de SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Servicio no disponible" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Abrir en el navegador" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/et.po000066400000000000000000001033271460766671100234400ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Estonian\n" "Language: et_EE\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: et\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "KDE Connecti teostus GNOMEle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnecti meeskond" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "GSConnecti abil saad sa turvaliselt ühenduda mobiilseadmete ja teiste töölaudadega, et:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Jagada faile, linke ja teksti" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Saata ja vastu võtta sõnumeid" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Sünkroonida lõikelaua sisu" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Sünkroonida kontakte" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Sünkroonida teatisi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Juhtida meediamängijaid" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Juhtida süsteemi helitugevust" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Käivitada eelmääratud käsklusi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Ja muud…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect GNOME Shellis" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Ühendu…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Loobu" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Ühenda" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP-aadress" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Kontakte pole" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Abi" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Kirjuta telefoninumber või nimi" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Muu" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Saada SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Saada" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Seade on lahti ühendatud" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Saada sõnum" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Kirjuta sõnum" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Sõnumi sisestamine" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Kirjuta sõnum ja vajuta saatmiseks Enter" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Sõnumside" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Uus vestlus" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Vestlused puuduvad" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Ühtegi vestlust pole valitud" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Vali või alusta vestlus" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Muuda käsklust" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Salvesta" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nimi" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Käsurida" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Vali käivitatav" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Ava" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Lauaarvuti" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kaamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Lõikelaua sünkroonimine" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Meediamängijad" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Hiir ja klaviatuur" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Helitugevuse juhtimine" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Failid" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Võta faile vastu" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Salvesta failid asukohta" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Jagamine" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Seadme aku" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Tühjeneva aku teade" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Teade kohandatud taseme laadimisest" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Täislaetud aku teade" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Süsteemi aku" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Jaga statistikat" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Aku" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Käsklused" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Lisa käsklus" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Jaga teateid" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Jaga, kui on aktiivne" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Rakendused" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Teated" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontaktid" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Sissetulevad kõned" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Helitugevus" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Peata meedia" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Käimasolevad kõned" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Vaigista mikrofon" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonifunktsioon" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Tegevuste otseteed" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Lähtesta kõik…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Otseteed" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Pluginad" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Katseline" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Seadme vahemälu" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Tühjenda vahemälu…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Pärand SMS-tugi" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFTP automaathaakimine" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Täpsemad" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Klaviatuuriotseteed" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Seadme seaded" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Paarita" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Seade on paaritamata" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Sa võid seda seadet enne paaritamist seadistada" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Krüpteeringu teave" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Eemalda paardumine" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Seadmesse" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Seadmest" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Puudub" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Taasta" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Vaiksem" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Vaigista" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Määra" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Vajuta tühistamiseks Esc või klaviatuuriotsetee lähtestamiseks Tagasilüke." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Seadme nimi" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "Nimeta ümber" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Värskenda" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobiiliseaded" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Teenusemenüü" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Seadmemenüü" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Muuda seadme nime" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Seadmed" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Seadmete otsimine…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Brauserilaiendused" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Luba" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "See seade on paardumata seadmetele nähtamatu" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Avastamine keelatud" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Ekraanirežiim" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Paneel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Kasutajamenüü" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Genereeri tugiteenusele logi" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "GSConnecti teave" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Vali seade" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Vali" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Seadet ei leitud" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Seadmete loend" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Teata" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Midagi läks valesti" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnectil esines ootamatu viga. Palun teata veast ja lisa mistahes infot, mis võib lahendamisel kaasa aidata." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Tehnilised andmed" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Saada mobiilseadmesse" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d ühendatud" msgstr[1] "%d ühendatud" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Muuda" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Eemalda" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Keelatud" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Sisesta %s muutmiseks uus otsetee" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s on juba kasutusel" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Täielik KDE Connect'i teostus GNOMEle" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Madis O, 2018." #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Silumissõnumid logitakse. Teosta mistahes vajalikud sammud probleemi taasloomiseks, seejärel vaata logi üle." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Vaata logi üle" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Sülearvuti" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Nutitelefon" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tahvelarvuti" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisioon" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Paaritamata" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Ühendus katkestatud" #: src/preferences/service.js:525 msgid "Connected" msgstr "Ühendatud" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Teenuse ootamine…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Klõpsa, et saada veaotsingul abi" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Klõpsa rohkema teabe saamiseks" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Helista telefoninumbrile" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Jaga faili" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Kuva saadaolevad seadmed" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Kuva kõik seadmed" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Sihtseade" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Sõnumi sisu" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Saada teade" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Teavitava rakenduse nimi" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Teate sisu" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Teate ikoon" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Teate ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Helise" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Jaga linki" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Jaga teksti" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Kuva väljalaskeversiooni" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-seade aadressil %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Paaritamistaotlus seadmelt %s" #: src/service/device.js:850 msgid "Reject" msgstr "Keeldu" #: src/service/device.js:855 msgid "Accept" msgstr "Nõustu" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Avastamine on keelatud selles võrgus olevate seadmete arvu tõttu." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSLi ei leitud" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Port on juba kasutusel" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Aku infovahetus" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: aku on täis" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Täielikult laetud" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Aku on laetud kohandatud tasemeni" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Laetud" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: aku on tühi" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% jäänud" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Lõikelaud" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Jaga lõikelaua sisu" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Lõikelaua saatmine" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Lõikelaua vastuvõtmine" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Ligipääs ühendatud seadme kontaktide juurde" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Leia mu telefon" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Helista oma seotud seadmele" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Hiirepadi" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Võimaldab seotud seadmel töötada hiire ja klaviatuurina" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Kahesuunaline meediumi kaugjuhtimine" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Teadmata" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Jaga teavitusi seotud seadmega" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Tühista teade" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Sulge teade" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Vasta teatele" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Aktiveeri teade" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Tee seotud seadmega pilti ning saada see arvutisse" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Ülekanne ebaõnnestus" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "„%s“saatmine seadmesse %s ebaõnnestus" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Pingide saatmine ja vastu võtmine" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Esitlus" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Kasuta seotud seadet presentatsiooniks" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Käivita käsklusi" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Käivita käsklusi seotud seadmel või luba seadmel käivitada ettemääratuid käske selles arvutis" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Sirvi seotud seadme failisüsteemi" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Haagi" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Haagi lahti" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s teatas vea" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Jaga" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "URL-i ja failide jagamine seadmete vahel" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s ei tohi faile üles laadida" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Faili edastamine" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "„%s“vastuvõtmine seadmelt %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Ülekanne edukas" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "„%s“vastu võetud seadmelt %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Ava fail" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "„%s“vastuvõtmine seadmest %s ebaõnnestus" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "%s poolt jagatud tekst" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Saadan „%s“seadmesse %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "„%s“saadetud seadmesse %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Saada faile seadmesse %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Ava, kui valmis" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Saada link seadmesse %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Saada ja loe seotud seadme SMS-e ning saa teadet uute SMS-ide kohta" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Uus SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Vasta SMSile" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Jaga SMSi" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Süsteemi helitugevus" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Luba ühendatud seadmel juhtida süsteemi helitugevust" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudiot ei leitud" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Saa teadet kõnede kohta ning reguleeri saabuvate/väljuvate kõnede helitugevust" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Vaigista kõne" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Tundmatu kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Sissetulev kõne" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Väljuv kõne" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Faks" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Töö" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobiil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Kodu" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Saada seadmesse %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Praegu" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Eile・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minutit" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Pole saadaval" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Grupisõnum" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Sina: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Ja %d teine kontakt" msgstr[1] "Ja %d teist" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Kaugklaviatuur ei ole seadmes %s aktiivne" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (hindamine…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (täitumiseni %d∶%02d)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (jäänud %d∶%02d)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Vasta" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Jaga linke GSConnectiga, otse brauserisse või SMSi teel." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Teenus pole saadaval" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Ava brauseris" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/fa.po000066400000000000000000001147101460766671100234140ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Persian\n" "Language: fa_IR\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: fa\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "جی‌اس‌کانکت" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "پیاده‌سازی کی‌دی‌ای کانکت برای گنوم" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "گروه جی‌اس‌کانکت" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "جی‌اس‌کانکت، پیاده‌سازی کاملی از کی‌دی‌ای کانکت، مخصوص پوستهٔ گنوم با یکپارچگی ناتیلوس، کروم Ùˆ ÙØ§ÛŒØ±Ùاکس است. گروه کی‌دی‌ای کانکت، برنامه‌هایی برای گنو/لینوکس، بی‌اس‌دی، اندروید، سیل‌Ùیش، آی‌اواس، مک‌او‌اس Ùˆ ویندوز دارند." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "با جی‌اس‌کانکت می‌توانید به صورت امن به Ø§ÙØ²Ø§Ø±Ù‡â€ŒÙ‡Ø§ÛŒ همراه Ùˆ دیگر میزکارهایتان وصل شوید تا:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "پرونده‌ها، پیوندها Ùˆ متن را هم‌رسانی کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "پیام‌ها را ÙØ±Ø³ØªØ§Ø¯Ù‡ Ùˆ Ø¯Ø±ÛŒØ§ÙØª کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "محتوای تخته‌گیره را همگام کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "آشنایان را همگام کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "آگاهی‌ها را همگام کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "پخش‌کننده‌های رسانه را وابپایید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "حجم صدای سامانه را وابپایید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "دستورهای از پیش تعریÙ‌شده را اجرا کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Ùˆ بیش‌تر…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "جی‌اس‌کانکت در پوستهٔ گنوم" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "وصل شدن به…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "لغو" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "وصل شدن" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "نشانی آی‌پی" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "بدون آشنا" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "راهنما" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "شماره تلÙÙ† یا نامی را بیازمایید" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "دیگر" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† پیامک" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù†" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Ø§ÙØ²Ø§Ø±Ù‡ قطع است" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† پیام" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "نوشتن یک پیام" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "ورودی پیام" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "پیامی نوشته Ùˆ برای ÙØ±Ø³ØªØ§Ø¯Ù†ØŒ ورود را بزنید" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "پیام‌رسانی" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Ú¯ÙØªâ€ŒÙˆÚ¯ÙˆÛŒ جدید" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "بدون Ú¯ÙØªâ€ŒÙˆÚ¯Ùˆ" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Ú¯ÙØªâ€ŒÙˆÚ¯ÙˆÛŒ گزیده نشده" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Ú¯ÙØªâ€ŒÙˆÚ¯ÙˆÛŒÛŒ را برگیده یا آغاز کنید" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "ØµÙØ­Ù‡â€ŒÙ„مسی.\n" "برای جابه‌جایی نشانگر موشی این‌جا بکشید.\n" "برای کشیدن نشانگر موشی طولانی Ù†Ú¯Ù‡ دارید.\n\n" "کلیک ساده به Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡ ÙØ±Ø³ØªØ§Ø¯Ù‡ خواهد شد.\n" "دکمه‌های راست، وسط، Ú†Ù¾ Ùˆ لغزنده‌های چرخی." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "ویرایش دستور" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "ذخیره" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "نام" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "خط ÙØ±Ù…ان" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "پروندهٔ اجرایی‌ای برگزینید" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "گشودن" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "میزکار" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "دوربین" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "همگام‌سازی تخته‌گیره" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "پخش‌کننده‌های رسانه" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "موشی Ùˆ ØµÙØ­Ù‡â€ŒÚ©Ù„ید" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "واپایش حجم صدا" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "پرونده‌ها" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Ø¯Ø±ÛŒØ§ÙØª پرونده‌ها" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "ذخیرهٔ پرونده‌ها در" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "هم‌رسانی" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "باتری Ø§ÙØ²Ø§Ø±Ù‡" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "آگاهی Ú©Ù… بودن باتری" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "تا آگاهی سطح Ø³ÙØ§Ø±Ø´ÛŒ شارژ می‌شود" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "آگاهی پر شدن کامل" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "باتری سامانه" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "هم‌رسانی آمار" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "باتری" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "دستورها" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Ø§ÙØ²ÙˆØ¯Ù† دستور" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "هم‌رسانی آگاهی‌ها" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "هم‌رسانی هنگام ÙØ¹Ù‘ال بودن در نشست" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "برنامه‌ها" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "آگاهی‌ها" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "آشنایان" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "تماس‌های Ø¯Ø±ÛŒØ§ÙØªÛŒ" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "حجم صدا" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Ù…Ú©Ø« رسانه" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "تماس‌های در حال انجام" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "خموشی میکروÙون" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "تلÙÙ†" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Ø§ÙØ²ÙˆØ¯Ù† میان‌بر" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "بازنشانی همه…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "میان‌برها" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Ø§ÙØ²Ø§ÛŒÙ‡â€ŒÙ‡Ø§" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "آزمایشی" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "انبارهٔ Ø§ÙØ²Ø§Ø±Ù‡" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "پاک‌سازی انباره…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "پشتیابن پیامک قدیمی" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "سوار کردن خودکار SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Ù¾ÛŒØ´â€ŒØ±ÙØªÙ‡" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "میان‌برهای ØµÙØ­Ù‡â€ŒÚ©Ù„ید" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "تنظیمات Ø§ÙØ²Ø§Ø±Ù‡" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Ø¬ÙØª کردن" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Ø§ÙØ²Ø§Ø±Ù‡ جدا شده" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "ممکن است بخواهید پیش از Ø¬ÙØª کردن، این Ø§ÙØ²Ø§Ø±Ù‡ را پیکربندی کنید" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "اطّلاعات رمزنگاری" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "جدا سازی" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "به Ø§ÙØ²Ø§Ø±Ù‡" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "از Ø§ÙØ²Ø§Ø±Ù‡" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "هیچ" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "بازگردانی" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Ú©Ù… کردن" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "خموش" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "تنظیم" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "برای بازنشانی میان‌بر ØµÙØ­Ù‡â€ŒÚ©Ù„ید، گریز یا پس‌بر را بزنید." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "نام Ø§ÙØ²Ø§Ø±Ù‡" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_تغییر نام" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "نوسازی" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "تنظیمات تلÙÙ† همراه" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Ùهرست خدمت" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Ùهرست Ø§ÙØ²Ø§Ø±Ù‡" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "ویرایش نام Ø§ÙØ²Ø§Ø±Ù‡" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Ø§ÙØ²Ø§Ø±Ù‡â€ŒÙ‡Ø§" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "در جست‌وجوی Ø§ÙØ²Ø§Ø±Ù‡â€ŒÙ‡Ø§â€¦" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Ø§ÙØ²ÙˆÙ†Ù‡â€ŒÙ‡Ø§ÛŒ مرورگر" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "به کار انداختن" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "این Ø§ÙØ²Ø§Ø±Ù‡ برای Ø§ÙØ²Ø§Ø±Ù‡â€ŒÙ‡Ø§ÛŒ Ø¬ÙØª نشده، نامریی است" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "کش٠از کار Ø§ÙØªØ§Ø¯" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "حالت نمایش" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "تابلو" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Ùهرست کاربر" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "ایجاد گزارش پشتیبانی" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "دربارهٔ جی‌اس‌کانکت" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Ø§ÙØ²Ø§Ø±Ù‡â€ŒØ§ÛŒ را برگزینید" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "گزینش" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "هیچ Ø§ÙØ²Ø§Ø±Ù‡â€ŒØ§ÛŒ پیدا نشد" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Ùهرست Ø§ÙØ²Ø§Ø±Ù‡" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "گزارش" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "چیزی اشتباه شد" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "جی‌ای‌کانکت با خطایی غیرمنتظره روبه‌رو شد. Ù„Ø·ÙØ§Ù‹ مشکل را گزارش داده Ùˆ هر اطّلاعاتی Ú©Ù‡ ممکن است Ú©Ù…Ú© کند را بدهید." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "جزییات ÙÙ†ÛŒ" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† به Ø§ÙØ²Ø§Ø±Ù‡Ù” همراه" #: src/extension.js:52 msgid "Sync between your devices" msgstr "همگام‌سازی میان Ø§ÙØ²Ø§Ø±Ù‡â€ŒÙ‡Ø§ÛŒØªØ§Ù†" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d وصل‌شده" msgstr[1] "%d وصل‌شده" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "ویرایش" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "برداشتن" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "از کار Ø§ÙØªØ§Ø¯Ù‡" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "برای تغییر %s میان‌بر جدیدی وارد کنید" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s از پیش در حال Ø§Ø³ØªÙØ§Ø¯Ù‡ است" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "پیاده‌سازی کامل کی‌دی‌ای کانکت برای گنوم" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "دانیال بهزادی " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "پیام‌های Ø±ÙØ¹ اشکال ثبت شده‌اند. هر اقدامی Ú©Ù‡ برای بازتولید مشکل لازم است را انجام داده، سپس گزارش را بررسی کنید." #: src/preferences/service.js:421 msgid "Review Log" msgstr "بازبینی گزارش" #: src/preferences/service.js:489 msgid "Laptop" msgstr "لپ‌تاپ" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "تلÙÙ† هوشمند" #: src/preferences/service.js:493 msgid "Tablet" msgstr "رایانک" #: src/preferences/service.js:495 msgid "Television" msgstr "تلویزیون" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "جدا شده" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "قطع شده" #: src/preferences/service.js:525 msgid "Connected" msgstr "وصل شده" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "در انتظار خدمت…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "برای راهنمایی برای عیب‌یابی کلیک کنید" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "برای اطّلاعات بیشتر کلیک کنید" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "شماره‌گیری" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "هم رسانی پرونده" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Ùهرست Ø§ÙØ²Ø§Ø±Ù‡â€ŒÙ‡Ø§ÛŒ موجود" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Ùهرست تمامی Ø§ÙØ²Ø§Ø±Ù‡â€ŒÙ‡Ø§" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Ø§ÙØ²Ø§Ø±Ù‡Ù” هدÙ" #: src/service/daemon.js:415 msgid "Message Body" msgstr "متن پیام" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† آگاهی" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "نام کارهٔ آگاهی" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "متن آگاهی" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "نقشک آگاهی" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "شناسهٔ آگاهی" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "عکس" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "پینگ" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "زنگ" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "هم‌رسانی پیوند" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "هم‌رسانی متن" #: src/service/daemon.js:532 msgid "Show release version" msgstr "نمایش نگارش ارائه" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "دستگاه بلوتوث در %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "کلید تأیید هویت: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "درخواست Ø¬ÙØª کردن از %s" #: src/service/device.js:850 msgid "Reject" msgstr "رد کردن" #: src/service/device.js:855 msgid "Accept" msgstr "پذیرش" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "به خاطر تعداد Ø§ÙØ²Ø§Ø±Ù‡â€ŒÙ‡Ø§ÛŒ روی این شبکه، کش٠از کار Ø§ÙØªØ§Ø¯." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "اوپن‌اس‌اس‌ال پیدا نشد" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "درگاه از پیش در حال Ø§Ø³ØªÙØ§Ø¯Ù‡ است" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "تبادل اطّلاعات باتری" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: باتری پر است" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "شارژ کامل شد" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: باتری به سطح شارژ شخصی رسید" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "â¦%d٪⩠شارژ شد" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: باتری Ú©Ù… است" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "â¦%d٪⩠مانده" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "تخته‌گیره" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "محتوای تخته‌گیره را هم‌رسانی کنید" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† به تخته‌گیره" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Ú¯Ø±ÙØªÙ† از تخته‌گیره" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "به مخاطبین دستگاه Ø¬ÙØªâ€ŒØ³Ø§Ø²ÛŒ شده دسترسی پیدا کنید" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "تلÙنم را بیاب" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "زنگ خوردن دستگاه Ø¬ÙØª شده شما" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "ØµÙØ­Ù‡Ù” موشی" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "قادر کردن Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡ برای عمل به عنوان موشی Ùˆ ØµÙØ­Ù‡â€ŒÚ©Ù„ید" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "ورودی دوردست" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "واپایش پخش رسانهٔ دوردست دوطرÙÙ‡" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "ناشناخته" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "هم‌رسانی آگاهی‌ها با Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "لغو آگاهی" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "بستن آگاهی" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "پاسخ به آگاهی" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "ÙØ¹Ø§Ù„ سازی آگاهی" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "درخواست از Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡ برای Ù†Ù…Ø§Ú¯Ø±ÙØª Ùˆ انتقالش به این رایانه" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "جابه‌جایی شکست خورد" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "شکست در ÙØ±Ø³ØªØ§Ø¯Ù† «%s» به %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "پینگ‌ها را ÙØ±Ø³ØªØ§Ø¯Ù‡ Ùˆ Ø¯Ø±ÛŒØ§ÙØª کنید" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "پینگ: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "ارائه" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Ø§Ø³ØªÙØ§Ø¯Ù‡ از Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡ به عنوان ارائه دهنده" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "اجرای دستورها" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "اجرای ÙØ±Ù…ان‌ها روی Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡â€ŒØ©Ø§Ù† یا اجازه به Ø§ÙØ²Ø§Ø±Ù‡ برای اجرای ÙØ±Ù…ان‌های از پیش تعریÙ شده روی این رایانه" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "مرور سامانه‌پروندهٔ Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "سوار کردن" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "پیاده کردن" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s خطایی گزارش کرد" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "هم‌رسانی" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "هم‌رسانی پرونده‌ها Ùˆ نشانی‌ها میان دستگاه‌ها" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s اجازهٔ بارگذاری پرونده‌ها را ندارد" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "در حال جابه‌جایی پرونده‌ها" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "در حال Ø¯Ø±ÛŒØ§ÙØª «%s» از %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "جابه‌جایی موÙÙ‚" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "«%s» از %s Ø¯Ø±ÛŒØ§ÙØª شد" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "نمایش مکان پرونده" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "گشودن پرونده" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "شکست در Ú¯Ø±ÙØªÙ† «%s» از %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "متن هم‌رسانده از %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "در حال ÙØ±Ø³ØªØ§Ø¯Ù† «%s» به %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "«%s» به %s ÙØ±Ø³ØªØ§Ø¯Ù‡ شد" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† پرونده‌ها به %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "گشودن هنگام اتمام" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† پیوندی به %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "پیامک" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† یا خواندن پیامک Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡ Ùˆ آگاه شدن از پیامک جدید" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "پیامک جدید (نشانی)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "پاسخ به پیامک" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "هم‌رسانی پیامک" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "حجم صدای سامانه" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "قادر کردن Ø§ÙØ²Ø§Ø±Ù‡Ù” Ø¬ÙØªÂ Ø´Ø¯Ù‡ برای واپایش حجم صدای سامانه" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "پالس‌آدیو پیدا نشد" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "آگاه شدن از تماس‌ها Ùˆ تنظیم حجم صدای سامانه در طول زنگ خوردن Ùˆ تماس" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "خموشی تماس" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "آشنای ناشناس" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "تماس Ø¯Ø±ÛŒØ§ÙØªÛŒ" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "تماس جاری" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "دورنگار" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "کاری" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "همراه" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "خانه" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "ÙØ±Ø³ØªØ§Ø¯Ù† به %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "هم‌اکنون" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "دیروز ・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d دقیقه" msgstr[1] "%d دقیقه" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "ناموجود" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "پیام گروهی" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "شما: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Ùˆ %d آشنای دیگر" msgstr[1] "Ùˆ %d آشنای دیگر" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "ØµÙØ­Ù‡â€ŒÚ©Ù„ید دوردست روی %s ÙØ¹Ù‘ال نیست" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "â¦%d٪⩠(در حال محاسبه…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "â¦%d٪⩠(â¦%d:%02d⩠تا پر شدن)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "â¦%d٪⩠(â¦%d:%02d⩠مانده)" #: src/shell/notification.js:58 msgid "Reply" msgstr "پاسخ" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "با جی‌اس کانکت، پیوندها را با پیامک یا مستقیماً در مرورگر هم‌رسانی کنید." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "خدمت ناموجود" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "گشودن در مرورگر" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/fi.po000066400000000000000000001054311460766671100234240ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-11 16:54\n" "Last-Translator: \n" "Language-Team: Finnish\n" "Language: fi_FI\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: fi\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "KDE Connect -toteutus GNOMElle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect-tiimi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "GSConnectin avulla voit muodostaa turvallisen yhteyden mobiililaitteisiin ja muihin tietokoneisiin:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Jaa tiedostoja, linkkejä ja tekstiä" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Lähetä ja vastaanota viestejä" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Synkronoi leikepöydän sisältö" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Synkronoi yhteystiedot" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Synkronoi ilmoitukset" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Ohjaa mediasoittimia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Ohjaa järjestelmän äänenvoimakkuutta" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Suorita ennaltamäärättyjä komentoja" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Ja lisää…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect GNOME Shellissä" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Yhdistä kohteeseen…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Peru" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Yhdistä" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP-osoite" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Ei yhteystietoja" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Ohje" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Kirjoita puhelinnumero tai nimi" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Muut" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Lähetä tekstiviestillä" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Lähetä" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Laite on kytketty irti" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Lähetä viesti" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Kirjoita viesti" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Viestin syöttö" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Kirjoita viesti ja paina Enter lähettääksesi" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Viestintä" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Uusi keskustelu" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Ei keskusteluja" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Ei keskusteluja valittuna" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Valitse tai aloita keskustelu" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Muokkaa komentoa" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Tallenna" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nimi" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Komentorivi" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Valitse käynnistystiedosto" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Avaa" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Työpöytä" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Leikepydän synkronointi" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Mediasoittimet" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Hiiri & näppäimistö" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Äänenvoimakkuuden säätö" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Tiedostot" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Vastaanota tiedostoja" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Tallenna tiedostot nimellä" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Jakaminen" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Laitteen akku" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Ilmoitus akun alhaisesta varaustasosta" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Mukautetun varaustason ilmoitus" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Ilmoitus akun täydestä varaustasosta" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Järjestelmän akku" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Jaa tilastot" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Akku" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Komennot" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Lisää komento" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Jaa ilmoitukset" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Jaa kun aktiivinen" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Sovellukset" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Ilmoitukset" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Yhteystiedot" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Saapuvat puhelut" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Äänenvoimakkuus" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Keskeytä media" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Meneillään olevat puhelut" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Mykistä mikrofoni" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Puhelinpalvelut" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Toimintojen pikanäppäimet" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Nollaa kaikki…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Pikanäppäimet" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Liitännäiset" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Kokeelliset" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Laitteen välimuisti" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Tyhjennä välimuisti…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Vanhentunut SMS-tuki" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFTP:n automaattinen liitäntä" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Lisäominaisuudet" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Pikanäppäimet" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Laitteen asetukset" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Muodosta laitepari" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Laitteen paritus on poistettu" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Voit määrittää tämän laitteen ennen pariliitoksen muodostamista" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Salaustiedot" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Poista paritus" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Laitteelle" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Laitteesta" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Ei mitään" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Palauta" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Madalla" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Mykistä" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Aseta" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Paina Esc peruuttaaksesi tai Backspace nollataksesi pikanäppäimen." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Laitteen nimi" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Nimeä uudelleen" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Päivitä" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobiiliasetukset" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Palveluvalikko" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Laitevalikko" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Muokkaa laitteen nimeä" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Laitteet" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Etsitään laitteita…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Laajennuksen asetukset" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect pysyy aktiivisena, kun Gnome on lukittu" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Selainten lisäosat" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Ota käyttöön" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Tämä laite on näkymätön laitteille, joiden kanssa ei ole muodostettu laiteparia" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Laitteiden löytö pois käytöstä" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Näyttötila" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Paneeli" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Käyttäjävalikko" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Luo tukiloki" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Tietoja GSConnectista" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Valitse laite" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Valitse" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Laitetta ei löytynyt" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Laiteluettelo" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Ilmoita" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Jokin meni pieleen" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect havaitsi odottamattoman virheen. Ilmoita ongelmasta ja liitä mukaan kaikki mahdolliset tiedot." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Tekniset yksityiskohdat" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Lähetä mobiililaitteeseen" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Synkronoi laitteidesi välillä" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d yhdistetty" msgstr[1] "%d yhdistetty" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Muokkaa" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Poista" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Poistettu käytöstä" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Anna uusi pikanäppäin muuttaaksesi %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s on jo käytössä" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Täydellinen KDE Connect -toteutus Gnomea varten" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Jiri Grönroos \n" "..." #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Vianmääritysviestit kirjataan. Tee kaikki tarvittavat toimenpiteet ongelman toistamiseksi ja tarkista sitten loki." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Tarkista loki" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Kannettava" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Älypuhelin" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tabletti" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisio" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Laiteparia ei muodostettu" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Yhteys katkaistu" #: src/preferences/service.js:525 msgid "Connected" msgstr "Yhdistetty" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Odotetaan palvelua…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Napsauta tästä vianmääritykseen" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Napsauta saadaksesi lisätietoja" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Näppäile numero" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Jaa tiedosto" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Listaa saatavilla olevat laitteet" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Listaa kaikki laitteet" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Kohdelaite" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Viestin runko" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Lähetä ilmoitus" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Ilmoitussovelluksen nimi" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Ilmoituksen runko" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Ilmoituksen kuvake" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Ilmoituksen tunniste" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Kuva" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Soita ääni" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Jaa linkki" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Jaa teksti" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Näytä julkaisuversio" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-laite osoitteessa %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Vahvistusavain: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Laiteparin muodostuspyyntö laitteelta %s" #: src/service/device.js:850 msgid "Reject" msgstr "Hylkää" #: src/service/device.js:855 msgid "Accept" msgstr "Hyväksy" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Laitehaku on poistettu käytöstä tässä verkossa olevien laitteiden lukumäärän vuoksi." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL:ää ei löytynyt" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Portti on jo käytössä" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Vaihda akkutietoa" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Akku täynnä" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Täysin ladattu" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Akku on saavuttanut mukautetun varaustason" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d% % ladattu" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Akku lähes tyhjä" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d% % jäljellä" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Leikepöytä" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Jaa leikepöydän sisältö" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Leikepöydän työntö" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Leikepöydän veto" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Käytä parilaitteen yhteystietoja" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Etsi puhelin" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Soita ääni laiteparissa" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Kosketuslevy" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Mahdollistaa parilaitteen toimia etähiirenä ja -näppäimistönä" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Etäsyöte" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Kaksisuuntainen etämedian toiston ohjaus" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Tuntematon" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Jaa ilmoitukset parilaitteen kanssa" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Peru ilmoitus" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Sulje ilmoitus" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Vastaa ilmoitukseen" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Aktivoi ilmoitus" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Pyydä laiteparia ottamaan valokuva ja siirtämään se tälle tietokoneelle" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Siirto epäonnistui" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Tiedoston “%s†lähettäminen laitteelle %s epäonnistui" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Lähetä ja vastaanota pingejä" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Pingaa: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Esitys" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Käytä laiteparia esityslaitteena" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Suorita komentoja" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Suorita komennot liitetyllä laitteellasi tai anna laitteen suorittaa etukäteen määriteltyjä komentoja tällä tietokoneella" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Selaa laiteparin tiedostojärjestelmää" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Liitä" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Irrota" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s ilmoitti virheestä" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Jaa" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Jaa tiedostoja ja URL-osoitteita laitteiden välillä" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "Laitteella %s ei ole lupaa lähettää tiedostoja" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Siirretään tiedostoa" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Vastaanotetaan “%s†laitteelta %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Siirto valmistui" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Vastaanotettiin “%s†laitteelta %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Näytä tiedoston sijainti" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Avaa tiedosto" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Tiedoston “%s†vastaanotto laitteelta %s epäonnistui" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Laitteen %s jakama teksti" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Lähetetään “%s†laitteelle %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Lähetettiiin “%s†laitteelle %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Lähetä tiedostoja laitteeseen %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Avaa kun valmis" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Lähetä linkki laitteeseen %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "Tekstiviesti" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Lähetä ja lue laiteparin tekstiviestejä ja vastaanota ilmoitus uusista tekstiviesteistä" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Uusi tekstiviesti (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Vastaa tekstiviestiin" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Jaa tekstiviesti" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Järjestelmän äänenvoimakkuus" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Käytä laiteparia järjestelmän äänenvoimakkuuden hallintaan" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudiota ei löytynyt" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Vastaanota ilmoitus puheluista ja säädä järjestelmän äänenvoimakkuutta saapuvien ja käynnissä olevien puhelujen aikana" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Mykistä puhelu" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Tuntematon yhteystieto" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Saapuva puhelu" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Käynnissä oleva puhelu" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Faksi" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Työ" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobiili" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Koti" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Lähetä numeroon %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Juuri nyt" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Eilen・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuutti" msgstr[1] "%d minuuttia" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Ei saatavilla" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Ryhmäviesti" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Sinä: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Ja %d toinen yhteystieto" msgstr[1] "Ja %d muuta" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Etänäppäimistö laitteella %s ei ole aktiivinen" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d% % (Arvioidaan…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d% % (%d∶%02d kunnes täynnä)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d% % (%d∶%02d jäljellä)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Vastaa" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Jaa linkkejä GSConnectin avulla suoraan selaimeen tai tekstiviestillä." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Palvelu ei ole käytettävissä" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Avaa selaimessa" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/fr.po000066400000000000000000001074401460766671100234370ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-11 21:06\n" "Last-Translator: \n" "Language-Team: French\n" "Language: fr_FR\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: fr\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Une implémentation de KDE Connect pour GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "L'équipe de GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect est une implémentation complète de KDE Connect, en particulier pour GNOME Shell avec l'intégration de Nautilus, Chrome et Firefox. L'équipe KDE Connect à des applications pour Linux, BSD, Android, Sailfish, iOS, macOS et Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Avec GSConnect, vous pouvez vous connecter en toute sécurité à des appareils mobiles et à d'autres ordinateurs de bureau à :" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Partager des fichiers, des liens et du texte" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Envoyer et recevoir des messages" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Synchroniser le contenu du presse-papiers" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Synchroniser les contacts" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Synchroniser les notifications" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Contrôler les lecteurs de médias" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Contrôle le volume du système" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Exécuter des commandes prédéfinies" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Et bien plus encore…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect dans GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Se connecter à…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Annuler" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Connecter" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Adresse IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "\"Aucun contact\"" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Aide" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Taper un numéro de téléphone ou un nom" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Autre" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Envoyer un SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Envoyer" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "L'appareil est déconnecté" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Envoyer le message" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Taper un message" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Entrée de message" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Tapez un message et appuyez sur Entrée pour envoyer" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Messagerie" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nouvelle conversation" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Aucune discussion" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Aucune conversation sélectionnée" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Choisir ou commencer une discussion" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Touchpad.\n" "Faites glisser cette zone pour déplacer le curseur de la souris.\n" "Appuyez sur long pour faire glisser le curseur de la souris.\n\n" "Un simple clic sera envoyé à un appareil apparié.\n" "À gauche, au milieu, à droite et à roue." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Modifier la commande" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Enregistrer" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nom" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Commande" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Choisir un exécutable" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Ouvrir" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Ordinateur de bureau" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Appareil photo" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Synchroniser le presse-papiers" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Lecteurs multimédia" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Souris et clavier" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Gestion du volume" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Fichiers" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Recevoir les fichiers" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Enregistrer les fichiers dans" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Partage" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Batterie de l'appareil" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Notification de batterie faible" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Notification de batterie à un niveau de charge personnalisé" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Notification de batterie entièrement chargée" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Batterie système" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Partager des statistiques" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Batterie" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Commandes" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Ajouter une commande" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Synchroniser les notifications" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Partager quand l'appareil est actif" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Applications" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notifications" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contacts" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Appels entrants" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Mettre les médias en pause" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Appels en cours" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Mettre le microphone en sourdine" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Téléphonie" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Raccourcis d'actions" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Tout réinitialiser…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Raccourcis" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Greffons" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Expérimental" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Cache de l'appareil" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Vider le cache…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Ancienne gestion des SMS" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Montage automatique SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avancé" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Raccourcis clavier" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Paramètres de l'appareil" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Associer" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "L'appareil n'est pas pairé" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Vous pouvez configurer cet appareil avant le pairage" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informations de chiffrement" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Dissocier" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Vers l'Appareil" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Depuis l'Appareil" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Ne rien faire" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Restaurer" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Réduire" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Mettre en sourdine" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Définir" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Appuyez sur Echap pour annuler ou sur Retour Arrière pour réinitialiser le raccourci clavier." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nom de l'appareil" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Renommer" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Rafraîchir" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Paramètres" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menu du service" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menu de l'appareil" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Modifier le nom de l'appareil" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Appareils" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Recherche d'appareils…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Paramètres de l'extension" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect reste actif lorsque le shell GNOME est verrouillé" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Extensions du navigateur" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Activer" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Cet appareil est invisible par les appareils non pairés" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Découverte désactivée" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Mode d'affichage" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Barre des tâches" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Menu utilisateur" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Générer des logs pour le support" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "À propos de GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Sélectionner un appareil" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Sélectionner" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Aucun appareil trouvé" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Liste des appareils" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Signaler" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Un problème est survenu" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect a rencontré une erreur inattendue. Veuillez signaler le problème et inclure toutes les informations qui pourraient nous aider." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Détails techniques" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Envoyer vers l'appareil mobile" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Synchronisez entre vos appareils" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d Connecté" msgstr[1] "%d Connectés" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Modifier" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Supprimer" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Désactivé" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Entrer un nouveau raccourci pour modifier %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s est déjà utilisé" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Une implémentation complète de KDE Connect pour GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Mickaël Coiraton " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Les messages de dépannage sont enregistrés. Faites le nécessaire pour reproduire le problème puis vérifiez le fichier de log." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Vérifier les logs" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Ordinateur portable" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablette" #: src/preferences/service.js:495 msgid "Television" msgstr "Télévision" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Dissocié" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Déconnecté" #: src/preferences/service.js:525 msgid "Connected" msgstr "Connecté" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "En attente du service…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Cliquer pour l'aide de dépannage" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Cliquer pour plus d'informations" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Composer le numéro" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Partager un fichier" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Liste des appareils disponibles" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Liste de tous les appareils" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Appareil cible" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Corps du message" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Envoyer la notification" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Nom de l'application de la notification" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Corps de la notification" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Icône de la notification" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID de la notification" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Photo" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Faire sonner" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Partager un lien" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Partager du texte" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Montrer la sortie de version" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Périphérique Bluetooth à %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Clé de vérification : %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Demande d'association depuis %s" #: src/service/device.js:850 msgid "Reject" msgstr "Rejeter" #: src/service/device.js:855 msgid "Accept" msgstr "Accepter" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "La découverte a été désactivée en raison du nombre de périphériques sur ce réseau." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL introuvable" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Port déjà utilisé" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Échanger les informations sur la batterie" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: la batterie est pleine" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Complètement chargé" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: La batterie a atteint le niveau de charge personnalisé" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Chargé" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: la batterie est faible" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d %% restant" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Presse-papiers" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Partager le contenu du presse-papiers" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Envoyer le presse-papiers" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Récupérer le presse-papiers" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Accéder aux contacts de l'appareil appairé" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Trouver mon téléphone" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Faire sonner l'appareil appairé" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Pavé tactile" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Permet à l'appareil appairé d'agir comme une souris et un clavier à distance" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Entrée distante" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Contrôle à distance bidirectionnel de lecture de médias" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Inconnu" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Partager les notifications avec l'appareil appairé" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Annuler la notification" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Fermer la notification" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Répondre à la notification" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Active la notification" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Demander à l'appareil appairé de prendre une photo et de la transférer vers ce PC" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Échec du transfert" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Échec d'envoi de « %s » vers %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Envoyer et recevoir des pings" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping : %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Présentation" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Utiliser l'appareil appairé comme présentateur" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Exécuter des commandes" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Exécutez des commandes sur votre périphérique appairé ou laissez le périphérique exécuter des commandes prédéfinies sur ce PC" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Parcourir le système de fichiers de l'appareil appairé" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Monter" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Démonter" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s a signalé une erreur" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Partage" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Partager des fichiers et des URL entre les appareils" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s n'est pas autorisé à télécharger des fichiers" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Transfert du fichier" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Réception de « %s » depuis %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Transfert réussi" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Reçu « %s » depuis %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Afficher l'emplacement du fichier" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Ouvrir le fichier" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Échec de réception de « %s » depuis %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Texte partagé par %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Envoi de « %s » vers %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "« %s » envoyé vers %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Envoyer des fichiers vers %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Ouvrir une fois fini" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Envoyer un lien vers %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Envoyer et lire les SMS de l'appareil appairé et être informé des nouveaux SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nouveau SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Répondre au SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Partager le SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volume du système" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Permettre à l'appareil appairé de contrôler le volume du système" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio introuvable" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Être notifié des appels et régler le volume du système pendant la sonnerie/les appels en cours" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Mettre l'appel en sourdine" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Contact inconnu" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Appel entrant" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Appel sortant" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Professionnel" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobile" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Domicile" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Envoyer vers %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "À l'instant" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Hier・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minute" msgstr[1] "%d minutes" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Indisponible" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Message de groupe" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Vous: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Et %d autre contact" msgstr[1] "Et %d autres" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Le clavier distant sur %s n'est pas actif" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d %% (estimation en cours…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d %% (%d∶%02d jusqu'à charge complète)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d %% (%d∶%02d restant)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Répondre" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Partagez des liens avec GSConnect, directement vers le navigateur ou par SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Service indisponible" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Ouvrir dans le navigateur" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/fy.po000066400000000000000000001024521460766671100234440ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Frisian\n" "Language: fy_NL\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: fy-NL\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "KDE Connect implementaasje foar GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect Team" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Mei GSConnect kinst feilich ferbine mei mobiele apperaten en oare kompûters om:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Triemen, ferwiizingen en tekst te ferstjoeren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Berjochten te ferstjoeren en binnen te krijen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Klemboerd ynhâld syngronisearje" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Kontakten syngronisearje" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Notifikaasjes te syngronisearjen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Media spilers te bestjoeren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Lûdsynstellings fan it systeem te feroarjen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Fan te foaren definiearre kommando's ût te fieren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "En meer…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect yn GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Ferbine mei…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Ôfbrekke" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Ferbine" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP Adres" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Gjin kontakten" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Help" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Fier in telefoannûmer of namme yn" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Oars" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "SMS Ferstjoere" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Ferstjoere" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Ferbining mei apperaat ferbrutsen" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Berjocht Ferstjoere" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Berjocht ynfiere" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Berjocht Ynfier" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Fier in berjocht yn en druk op Enter om te ferstjoeren" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Berjochten" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nij Petear" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Gjin Petearen" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Gjin petear selektearre" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Selektear of begjin in petear" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Kommando Feroarje" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Bewarje" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Namme" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Kommandorigel" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Útfierbere triem selektearje" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Iepenje" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Desktop" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Klemboerd Syngronisaasje" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Mediaspilers" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Mûs & Toetseboerd" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Lûdsynstellings" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Triemen" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Triemen Ûntfange" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Triemen bewarje yn" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Diele" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Apperaat batterij" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Lege Batterij Notifikaasje" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Folslein Opladen Notifikaasje" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Systeem Batterij" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Statistiken Diele" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Baterij" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Kommandos" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Kommando Tafoegje" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Notifikaasjes Diele" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Diele Wannear Aktyf" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Applikaasjes" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notifikaasjes" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontakten" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Ynkommende Tillefoantsjes" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Lûdsynstellingen" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Media Skoftsje" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Útgeande Tillefoantsjes" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Mikrofoan bedimje" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefoan" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Aksje Fluchtoetsen" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Alles Werom Sette…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Ferkoartingen" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Plugins" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Eksperimenteel" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Apparaat Cache" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Cache leechje…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Efterhelle SMS ûndersteuning" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFTP Automatysk ferbine" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avansearre" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Toetseboerd Fluchtoetsen" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Apparaat Ynstellingen" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Keppelje" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Apparaat is net keppele" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Do kinst dit apparaat konfigurearje foar it keppeljen" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Fersifering Ynformaasje" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Ûntkeppelje" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Nei Apparaat" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Fan Apparaat" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Neat" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Werom Sette" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Omleech" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Bedimje" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Sette" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Druk op Esc om ôf te brekken as Backspace om de fluchtoets opnij yn te stellen." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Apparaat Namme" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "He_Rnimme" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Fernije" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobiele Ynstellingen" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Tsjinst Menu" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Apparaat Menu" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Apparaat Namme Feroarje" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Apparaten" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Om apparaten sykje…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Browser Add-Ons" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Ynskeakelje" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Dit apparaat is net sichtber foar net keppele apparaten" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Ûntdekken Útskeakelje" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Werjaan Mode" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Paniel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Brûkers menu" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Stipe logboek generearje" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Oer GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Apparaat Selektearje" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Selektearje" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Gjin Apparaat Fûn" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Apparaat list" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Oanjaan" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Der is wat mislearre" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect is tsjin in probleem oanrûn. Jou dit graach oan mei alle informaasje dy't brûkber wéze kin." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Technyske Details" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Nei Mobiel Apparaat ferstjoere" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "" msgstr[1] "%d Ferbûn" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Feroarje" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Fuortsmite" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Útskeakele" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "In nije fluchtoets ynfiere om %s te feroarjen" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s wurd ol brûkt" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "In folsleine KDE Connect implementaasje foar GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Tjipke van der Heide " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Debug berjochten wurde bewarre. Nim de nediche stappen om it probleem harrensels foardwaan te litten en besjoch don it loch." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Loch Besjen" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Snoadtillefoan" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Telefyzje" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Net Keppele" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Ferbining ferbrutsen" #: src/preferences/service.js:525 msgid "Connected" msgstr "Ferbûn" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Op in ferbining oan it wachtsjen…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Nûmer Skilje" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Triem Ferstjoere" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Beskikbere apparaten sjen litte" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Olle apparaten sjen litte" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Doel Apparaat" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Berjocht Ynhâld" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Notifikaasje Ferstjoere" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Notifikaasje Applikaasje Namme" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Notifikaasje Haadtekst" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Notifikaasje ôfbylding" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Notifikaasje ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Ôfbylding" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Ôfgean Litte" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Keppeling Diele" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Tekst Diele" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Útjefte Ferzje Sjen Litte" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Keppelingsfersyk fan %s" #: src/service/device.js:850 msgid "Reject" msgstr "Ôfslaan" #: src/service/device.js:855 msgid "Accept" msgstr "Tastean" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Discovery is útskeakele troch it oantal mobiele apparaten op dit netwurk." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL net fûn" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Poarte wurd ol brûkt" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Batterij is fol" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Foslein Opladen" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Batterij is leech" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% te gean" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Klemboerd" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Klemboerd Ferstjoere" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Klemboerd Ophelje" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Fyn Myn Tillefoan" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Mûsmatte" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Ûnbekend" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Notifikaasje Ôfbrekke" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Notifikaasje Ôfslûte" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Notifikaasje foar Antwurdzjen" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Notifikaasjes Ynskeakelje" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Oerdracht Mislearre" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Ferstjoeren fan \"%s\" nei %s mislearre" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Presentaasje" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Komandos Útfiere" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Oankeppelje" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Loskeppelje" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Diele" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s mei gjin triemen uploade" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Triemen Oersette" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "\"%s\" fan %s ûntfong" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Oerset Slagge" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "\"%s\" fan %s ûntfong" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Triem Iepenje" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Koe \"%s\" net fan %s binnen krije" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Tekst Dielt Troch %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "\"%s\" nei %s Oan't Stjoeren" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "“%s†nei %s ferstjoere" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Triemen nei %s ferstjoere" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Iepenje wannear klear" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Keppeling nei %s ferstjoere" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nije SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Op SMS Reagearje" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "SMS Diele" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Systeem Lûdsynstellings" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio net fûn" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Tillefoantsje Bedimje" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Ûnbekend Kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Binnenkommende Tillefoantsjes" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Útgeand Tillefoantsje" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Faks" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Wurk" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobyl" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Thûs" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Ferstjoer nei %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "No krekt" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Juster・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minút" msgstr[1] "%d minuten" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Net beskikber" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Berjochten Groepearje" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Do: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "En %d oar kontakt" msgstr[1] "En %d oare kontakten" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Toetseboerd op ôfstân op %s is net aktyf" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Te gean…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d Tot Fol)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d Te Gean)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Reagearje" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Keppelingen mei GSConnect ferstjoere, direkt nei de browser of mei SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Tsjinst Ûnbeskikber" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Yn Browser Iepenje" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/gl.po000066400000000000000000001027401460766671100234300ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Galician\n" "Language: gl_ES\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: gl\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementación do KDE Connect para GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Equipo GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Con GSConnect pode conectar con seguranza con dispositivos móbiles e outros escritorio para:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Compartir ficheiros, ligazóns e texto" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Enviar e recibir mensaxes" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Sincronizar contido do portapapeis" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Sincronizar contactos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Sincronizar notificacións" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Controlar reprodutores multimedia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Controla o volume do sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Executar ordes predefinidas" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "E máis…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect na Shell de GNOME" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Conectar con…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Anular" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Conectar" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Enderezo IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Sen contactos" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Axuda" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Escriba o número de teléfono ou nome" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Outro" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Enviar SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Enviar" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "O dispositivo está desconectado" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Enviar mensaxe" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Escriba unha mensaxe" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Entrada de mensaxe" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Escriba unha mensaxe e prema Intro para enviala" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Mensaxes" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nova conversa" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Sen conversas" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Non se seleccionou conversa" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Seleccionar ou comezar conversa" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Editar a orde" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Gardar" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nome" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Liña de ordes" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Escoller un executábel" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Abrir" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Escritorio" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Cámara" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Sincronizar portapapeis" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Reprodutores multimedia" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Rato e teclado" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Control de volume" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Ficheiros" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Recibir ficheiros" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Gardar ficheiros en" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Compartindo" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Batería do dispositivo" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Notificación de batería baixa" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Notificación de batería a tope de carga" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Batería do sistema" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Compartir estatísticas" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Batería" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Ordes" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Engadir orde" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Compartir notificacións" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Compartir cando estea activo" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Aplicativos" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notificacións" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contactos" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Chamadas entrantes" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Deter reprodución" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Chamadas saíntes" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Silenciar micrófono" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonía" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Atallos de acción" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Reiniciar todo…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Atallos" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Engadidos" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimental" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Caché do dispositivo" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Despexar a caché…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Compatibilidade con SMS herdados" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Montado automático SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avanzado" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Atallos de teclado" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Configuración do dispositivo" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Enparellar" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "O dispositivo non está emparellado" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Pode configurar este dispositivo antes do emparellado" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Info de cifrado" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Desemparellar" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "A dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Do dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Ningún" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Baixar" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Silencio" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Definir" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Premer Esc para anular ou Retroceso para redefinir o atallo de teclado." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nome do dispositivo" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Renomear" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Actualizar" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Configuración móbil" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menú de servizo" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menú do dispositivo" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Editar o nome do dispositivo" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Dispositivos" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Buscando dispositivos…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Engadidos de navegador" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Activar" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Este dispositivo é invisíbel para dispositivos non emparellados" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Descuberta desactivada" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Modo de visualización" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Menú do usuario" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Xerar o rexistro para asistencia" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Verbo de GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Seleccionar un dispositivo" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Seleccionar" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Non se atopou o dispositivo" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Lista do dispositivo" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Informar" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Algo foi mal" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect atopou un erro inesperado. Informe do problema e inclúa toda información que poida ser de axuda." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Detalles técnicos" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Enviar a dispositivo móbil" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d Conectado" msgstr[1] "%d Conectados" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Editar" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Retirar" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Desactivado" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Escribir un novo atallo para cambiar%s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s xa está en uso" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Unha implementación completa de KDE Connect para GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "tradutor" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Estanse a rexistrar as mensaxes de depuración. Faga o necesario para reproducir o problema e logo revise o rexistro." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Revisar o rexistro" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Portátil" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Móbil" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tableta" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisión" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Desemparellado" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Desconectado" #: src/preferences/service.js:525 msgid "Connected" msgstr "Conectado" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Agardar polo servizo…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Prema para obter axuda de solucións" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Premer para ter máis información" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Número en dial" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Compartir ficheiro" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Lista de dispositivos dispoñíbeis" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Listar todos os dispositivos" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Dispositivo de destino" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Corpo da mensaxe" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Enviar a notificación" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Nome da app notificada" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Corpo da notificación" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Icona da notificación" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID de notificación" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Timbre" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Compartir ligazón" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Compartir texto" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Amosar a versión da edición" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositivo bluetooth en %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Petición de emparellado desde %s" #: src/service/device.js:850 msgid "Reject" msgstr "Rexeitar" #: src/service/device.js:855 msgid "Accept" msgstr "Aceptar" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "A descuberta desactivouse debido ao número de dispositivos nesta rede." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "Non se atopou o OpenSSL" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "O porto xa está sendo utilizado" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: A batería está chea" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Carga completa" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: A batería está baixa" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "Queda o %d%%" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Portapapeis" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Entregar do portapapeis" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Recoller no portapapeis" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Atopar o meu teléfono" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Ãrea de rato" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Descoñecido" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Anular a notificación" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Pechar a notificación" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Responder a notificación" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Activar a notificación" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Fallou a transferencia" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Fallou o envío de \"%s\" a %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Presentación" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Executar ordes" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Compartir" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s non permite cargar ficheiros" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Transferindo o ficheiro" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Recibindo \"%s\" de %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Transferencia correcta" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Recibíronse \"%s\" desde %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Abrir ficheiro" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Fallou a recepción de \"%s\" desde %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Texto compartido por %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Enviando \"%s\" a %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Enviar \"%s\" a %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Enviar ficheiros a %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Abrir cando estea feito" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Enviar unha ligazón a %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Novo SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Responder a SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Compartir SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volume do sistema" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "Non se atopou PulseAudio" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silenciar chamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Contacto descoñecido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Chamada entrante" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Chamada en curso" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Traballo" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Móbil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Casa" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Enviar a %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Agora mesmo" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Onte・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Non dispoñíbel" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Mensaxe de grupo" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Vostede: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "E %d outro contacto" msgstr[1] "E %d outros" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "O teclado remoto de %s non está activo" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Estímase…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d:%02d Ata completar)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d Restante)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Compartir ligazóns con GSConnect, directamente co navegador ou por SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Servizo non dispoñíbel" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Abrir no navegador" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/he.po000066400000000000000000001110571460766671100234230ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-01-02 09:31-0500\n" "PO-Revision-Date: 2024-01-28 20:23\n" "Last-Translator: \n" "Language-Team: Hebrew\n" "Language: he_IL\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 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: he\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:30 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "מימוש של KDE Connect ל־GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "צוות GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "â€GSConnect ×”×•× ×ž×™×ž×•×© ×©×œ× ×©×œ KDE Connect במיוחד עבור מעטפת GNOME ×¢× ×©×™×œ×•×‘ קבצי×, Chrome ו־Firefox. לצוות של KDE Connect יש ×™×™×©×•×ž×™× ×œÖ¾Linux, BSD, Android, Sailfish, iOS, macOS ו־Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "×¢× GSConnect ב×פשרותך להתחבר בצורה מ×ובטחת ×œ×ž×›×©×™×¨×™× ×”× ×™×™×“×™× ×©×œ×š ולשלוחנות עבודה ××—×¨×™× ×ל:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "שיתוף קבצי×, ×§×™×©×•×¨×™× ×•×˜×§×¡×˜" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "שליחה וקבלת הודעות" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "סנכרון תוכן לוח הגזירי×" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "סנכרון ×נשי קשר" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "סנכרון התרעות" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "שליטה בפקדי המדיה" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "שליטה בעצמת השמע של המערכת" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "הפעלת פקודות מוגדרות מר×ש" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "ועוד…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:133 msgid "GSConnect in GNOME Shell" msgstr "GSConnect במעטפת GNOME" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "התחברות ×ל…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:405 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "ביטול" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "התחברות" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "כתובת IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "×ין ×נשי קשר" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "עזרה" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "יש להזין מספר טלפון ×ו ש×" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "×חרי×" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:293 #: src/service/daemon.js:407 src/service/plugins/sms.js:64 #: webextension/gettext.js:42 msgid "Send SMS" msgstr "שליחת מסרון SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "שליחה" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "ההתקן מנותק" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "שליחת הודעה" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 msgid "Type a message" msgstr "יש להקליד הודעה" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "שדה הודעה" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "יש להקליד הודעה וללחוץ Enter לשליחה" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "הודעות" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 msgid "New Conversation" msgstr "התכתבות חדשה" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "×ין התכתבויות" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "×œ× × ×‘×—×¨×” התכתבות" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "יש לבחור ×ו להתחיל התכתבות" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "משטח מגע.\n" "יש לגרור על ×זור ×–×” להזזת סמן העכבר.\n" "× × ×œ×œ×—×•×¥ לזמן ×רוך על מנת לגרור ולשחרר ×ת סמן העכבר.\n\n" "לחיצה פשוטה תישלח למכשיר המצומד.\n" "כפתור שמ×לי, ×מצעי וימני, כמו ×’× ×’×œ×™×œ×”." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "עריכת פקודה" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "שמירה" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "ש×" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "שורת פקודה" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "בחירת קובץ שניתן להרצה" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "פתיחה" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:482 msgid "Desktop" msgstr "שולחן עבודה" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "סנכרון לוח הגזירי×" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "פקדי מדיה" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "עכבר ומקלדת" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "עצמת שמע" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:334 msgid "Files" msgstr "קבצי×" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "קבלת קבצי×" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "שמירת ×§×‘×¦×™× ×ל" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "שיתוף" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "סוללת המכשיר" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "התרעה על סוללה חלשה" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "התרעה על טעינה ברמה מסוימת" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "התרעה על טעינה מל××”" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "סוללת מערכת" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "סטטיסטיקת שיתוף" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:16 msgid "Battery" msgstr "סוללה" #: data/ui/preferences-device-panel.ui:916 #: data/ui/preferences-device-panel.ui:1001 #: data/ui/preferences-device-panel.ui:2262 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "פקודות" #: data/ui/preferences-device-panel.ui:975 msgid "Add Command" msgstr "הוספת פקודה" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1063 msgid "Share Notifications" msgstr "שיתוף התרעות" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "שיתוף בזמן פעילות" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "יישומי×" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "התרעות" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "×נשי קשר" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "שיחות נכנסות" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "עצמת שמע" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "השהיית מדיה" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "שיחות פעילות" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "השתקת מיקרופון" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "טלפוניה" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "פעולות לצירופי מקשי×" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "×יפוס הכל…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "צירופי מקשי×" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "תוספי×" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "ניסיוני" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "מטמון מכשיר" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "ניקוי מטמון…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "תמיכה ישנה ב־SMS" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "עיגון SFTP ×וטומטי" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "מתקד×" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "צירופי מקשי×" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "הגדרות מכשיר" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2508 #: data/ui/preferences-device-panel.ui:2600 src/service/daemon.js:386 msgid "Pair" msgstr "מצומד" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "המכשיר ×œ× ×ž×¦×•×ž×“" #: data/ui/preferences-device-panel.ui:2555 msgid "You may configure this device before pairing" msgstr "יתכן והגדרת מכשיר ×–×” לפני צימוד" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2595 src/preferences/device.js:391 msgid "Encryption Info" msgstr "מידע על הצפנה" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:395 msgid "Unpair" msgstr "ביטול צימוד" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "למכשיר" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "מהמכשיר" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "כלו×" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "שחזור" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "חלש ביותר" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2657 #: data/ui/preferences-device-panel.ui:2690 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "השתקה" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "הגדרה" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "יש ללחוץ Esc לביטול ×ו Backspace ל×יפוס קיצורי המקשי×." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "×©× ×”×ª×§×Ÿ" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_שינוי ש×" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "רענון" #: data/ui/preferences-window.ui:139 src/extension.js:114 msgid "Mobile Settings" msgstr "הגדרות ×ž×›×©×™×¨×™× × ×™×™×“×™×" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "תפריט שירות" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "תפריט מכשיר" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "עריכת ×©× ×ž×›×©×™×¨" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "מכשירי×" #: data/ui/preferences-window.ui:328 src/preferences/service.js:644 msgid "Searching for devices…" msgstr "מחפש מכשירי×…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "הגדרות הרחבות" #: data/ui/preferences-window.ui:390 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "הש×רת GSConnect בפעולה בזמן נעילת מעטפת GNOME" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "×ª×•×¡×¤×™× ×œ×“×¤×“×¤×Ÿ" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "מ×ופשר" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "מכשיר ×–×” בלתי נר××” ×œ×ž×›×©×™×¨×™× ×©×œ× ×ž×¦×•×ž×“×™×" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "גילוי מושבת" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "מצב תצוגה" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "לוח" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "תפריט משתמש" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:402 msgid "Generate Support Log" msgstr "יומן תמיכה כללי" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "על ×ודות GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "בחירת מכשיר" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "בחירה" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:38 msgid "No Device Found" msgstr "×œ× × ×ž×¦×ו מכשירי×" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "רשימת מכשירי×" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "דיווח" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "משהו השתבש" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "â€GSConnect חווה שגי××” בלתי צפויה. × × ×œ×“×•×•×— על הבעיה ולצרף כל מידע שעשוי לסייע." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "×¤×¨×˜×™× ×˜×›× ×™×™×" #. TRANSLATORS: Send to , for file manager #. context menu #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: nautilus-extension/nautilus-gsconnect.py:187 src/service/ui/contacts.js:509 #: src/service/ui/contacts.js:524 #, python-format, javascript-format msgid "Send to %s" msgstr "שליחה ×ל %s" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:194 webextension/gettext.js:34 msgid "Send To Mobile Device" msgstr "שליחה למכשיר נייד" #: src/extension.js:50 msgid "Sync between your devices" msgstr "סנכרון בין ×”×ž×›×©×™×¨×™× ×©×œ×š" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "×חד מחובר" msgstr[1] "" msgstr[2] "%d ×ž×›×©×™×¨×™× ×ž×—×•×‘×¨×™×" msgstr[3] "%d מחוברי×" #: src/preferences/device.js:670 src/preferences/device.js:676 msgid "Edit" msgstr "עריכה" #: src/preferences/device.js:685 src/preferences/device.js:691 msgid "Remove" msgstr "הסרה" #: src/preferences/device.js:945 src/preferences/device.js:973 msgid "Disabled" msgstr "מושבת" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "יש להזין צירופי ×ž×§×©×™× ×œ×©×™× ×•×™ %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "â€%s כבר בשימוש" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "×™×™×©×•× ×ž×œ× ×©×œ KDE Connect עבור GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "יוסף ×ור בוצ׳קו " #: src/preferences/service.js:403 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "החל ×¨×™×©×•× ×™×•×ž×Ÿ הודעות לניפוי שגי×ות. × × ×œ×‘×¦×¢ ×ת ×”×¦×¢×“×™× ×”× ×“×¨×©×™× ×¢×œ מנת לשחזר ×ת הבעיה ול×חר מכן לסקור ×ת היומן." #: src/preferences/service.js:406 msgid "Review Log" msgstr "סקירת יומן" #: src/preferences/service.js:474 msgid "Laptop" msgstr "מחשב נייד" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "טלפון ×—×›×" #: src/preferences/service.js:478 msgid "Tablet" msgstr "מחשב לוח" #: src/preferences/service.js:480 msgid "Television" msgstr "טלויזיה" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "×œ× ×ž×¦×•×ž×“" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "מנותק" #: src/preferences/service.js:510 msgid "Connected" msgstr "מחובר" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "ממתין לשרת…" #: src/service/daemon.js:194 msgid "Click for help troubleshooting" msgstr "יש ללחוץ לעזרה בטיפול בבעיות" #: src/service/daemon.js:205 msgid "Click for more information" msgstr "יש ללחוץ למידע נוסף" #: src/service/daemon.js:299 msgid "Dial Number" msgstr "חיוג מספר" #: src/service/daemon.js:305 src/service/daemon.js:494 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "שיתוף קובץ" #: src/service/daemon.js:356 msgid "List available devices" msgstr "רשימת ×ž×›×©×™×¨×™× ×–×ž×™× ×™×" #: src/service/daemon.js:365 msgid "List all devices" msgstr "רשימת כל ×”×ž×›×©×™×¨×™× ×”×–×ž×™× ×™×" #: src/service/daemon.js:374 msgid "Target Device" msgstr "מכשיר יעד" #: src/service/daemon.js:416 msgid "Message Body" msgstr "גוף ההודעה" #: src/service/daemon.js:428 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "שליחת התרעה" #: src/service/daemon.js:437 msgid "Notification App Name" msgstr "×©× ×™×™×©×•× ×œ×”×ª×¨×¢×”" #: src/service/daemon.js:446 msgid "Notification Body" msgstr "גוף התרעה" #: src/service/daemon.js:455 msgid "Notification Icon" msgstr "סמליל התרעה" #: src/service/daemon.js:464 msgid "Notification ID" msgstr "מזהה התרעה" #: src/service/daemon.js:473 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "פינג" #: src/service/daemon.js:482 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "צלצול" #: src/service/daemon.js:503 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "שיתוף קישור" #: src/service/daemon.js:512 src/service/plugins/share.js:41 msgid "Share Text" msgstr "שיתוף טקסט" #: src/service/daemon.js:524 msgid "Show release version" msgstr "הצגת גרסת שחרור" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "התקן בלוטות׳ ב־%s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "×ימות מפתח: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:848 #, javascript-format msgid "Pair Request from %s" msgstr "בקשת צימוד מ־%s" #: src/service/device.js:855 msgid "Reject" msgstr "דחייה" #: src/service/device.js:860 msgid "Accept" msgstr "×ישור" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "הגילוי הושבת עקב מספר ×”×ž×›×©×™×¨×™× ×¢×œ הרשת." #: src/service/backends/lan.js:169 msgid "OpenSSL not found" msgstr "†OpenSSL ×œ× × ×ž×¦×" #: src/service/backends/lan.js:462 msgid "Port already in use" msgstr "פתחה כבר בשימוש" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "החלפת המידע על הסוללה" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "â€%s: הסוללה מל××”" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:119 msgid "Fully Charged" msgstr "טעינה מל××”" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "â€%s: הסוללה ×”×’×™×¢×” לרמת טעינה נבחרת" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "â€%d%% טעון" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "â€%s: הסוללה חלשה" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "נותרו %d%%" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "לוח הגזירי×" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "שיתוף תוכן לוח הגזירי×" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "שליחת לוח הגזירי×" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "קבלת לוח הגזירי×" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "גישה ל×נשי הקשר של המכשיר המצומד" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "מצי×ת הטלפון שלי" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "צלצול לטלפון המצומד שלך" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "משטח לעכבר" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "ל×פשר מכשיר המצומד לשמש כעכבר ומקלדת מרוחקי×" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "קלט מרוחק" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "בקרת הפעלת מדיה מרחוק דו־כיוונית" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "×œ× ×™×“×•×¢" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "שיתוף התרעות ×¢× ×”×ž×›×©×™×¨ המצומד" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "ביטול התרעה" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "סגירת התרעה" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "תגובה בהודעה" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "הפעלת התרעה" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "שליחה וקבלת ×ות (פינג)" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "פינג: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "מצגת" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "שימוש במכשיר המצומד כמצגת" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "הרצת פקודות" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "הרצת פקודות על המכשיר המצומד שלך ×ו לתת למכשר להריץ פקודות מוגדרות מר×ש על המחשב השולחני ×”×–×”" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "עיון במערכת ×”×§×‘×¦×™× ×©×œ המכשיר המצומד" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "עיגון" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "ביטול עיגון" #: src/service/plugins/sftp.js:193 #, javascript-format msgid "%s reported an error" msgstr "â€%s דיווח על שגי××”" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "שיתוף" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "שיתוף ×§×‘×¦×™× ×•×›×ª×•×‘×•×ª URL בין מכשירי×" #: src/service/plugins/share.js:132 src/service/plugins/share.js:208 #: src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "ההעברה כשלה" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "â€%s ×œ× ×ž×פשר להעלות קבצי×" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "מעביר קובץ" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "â€â€ž%s†מתקבל מ־„%sâ€" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "ההעברה הצליחה" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "â€â€ž%s†התקבל מ־„%sâ€" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "הצגת ×ž×™×§×•× ×”×§×•×‘×¥" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "פתיחת הקובץ" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "×רע כשל בקבלת â€â€ž%s†מ־„%sâ€" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "טקסט משותף על ידי %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "â€â€ž%s†נשלח כעת ×ל „%sâ€" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "â€â€ž%s†נשלח ×ל „%sâ€" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "×ירעה שגי××” בשליחת „%s†×ל „%sâ€" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "שליחת ×§×‘×¦×™× ×ל %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "פתיחה ×¢× ×¡×™×•×" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "שליחת קישור ×ל %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "מסרון SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "שליחת וקרי×ת מסרוני SMS של המכשיר המצומד וקבלת התרעות על מסרון חדש" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "מסרון SMS חדש (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "לענות ל־SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "שיתוף SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "עצמת השמע של המערכת" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "ל×פשר למכשיר המצומד ×œ×©×œ×•× ×‘×¢×¦×ž×ª השמע של המערכת" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "â€PulseAudio ×œ× × ×ž×¦×" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "לקבל התרעות על שיחות והת×מת עצמת השמע של המערכת בזמן צלצול ×ו שיחה מתמשכת" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "השתקת שיחה" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "×יש קשר ×œ× ×™×“×•×¢" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "שיחה נכנסת" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "שיחה פעילה" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "פקס" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "עבודה" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "טלפון נייד" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "בית" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "ממש עכשיו" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "×תמול %s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "לפני %d דקות" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "×œ× ×–×ž×™×Ÿ" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "הודעה קבוצתית" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "×× ×™: %s" #: src/service/ui/messaging.js:959 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "" msgstr[1] "" msgstr[2] "הוספת %d ×נשי קשר נוספי×" msgstr[3] "ו Ö¾%d ×נשי קשר נוספי×" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "מקלדת מרוחקת על %s ××™× ×” פעילה" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:124 #, javascript-format msgid "%d%% (Estimating…)" msgstr "â€â€Ž%d%% (הערכה…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:133 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "â€â€Ž%d%% (‎%d∶%02d‎ â€×¢×“ טעינה מל××”)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "â€â€Ž%d%% (‎%d∶%02d‎ â€× ×•תרו)" #: src/shell/notification.js:69 msgid "Reply" msgstr "להשיב" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "שיתוף ×§×™×©×•×¨×™× ×¢× GSConnect, ישירות לדפדפן ×ו על ידי מסרון SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:36 msgid "Service Unavailable" msgstr "שרת ×œ× ×–×ž×™×Ÿ" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "פתיחה בדפדפן" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/hu.po000066400000000000000000001077311460766671100234470ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-06-29 13:25\n" "Last-Translator: \n" "Language-Team: Hungarian\n" "Language: hu_HU\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: hu\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "KDE Connect implementáció GNOME-hoz" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "A GSConnect csapata" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "A GSConnect egy teljes KDE Connect implementáció, fÅ‘ként a GNOME Shell-hez, Nautilus, Chrome és Firefox integrációval. A KDE Connect csapatának alkalmazásai elérhetÅ‘ek Linux, BSD, Android, Sailfish, iOS, macOS és Windows rendszereken." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "A GSConnect segítségével biztonságosan csatlakozhat mobileszközökhöz és másik számítógépekhez a következÅ‘khöz:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Fájlok, hivatkozások, szövegek megosztása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Üzenetek küldése és fogadása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Vágólap tartalmának szinkronizálása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Névjegyek szinkronizálása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Értesítések szinkronizálása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Médialejátszók irányítása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "RendszerhangerÅ‘ vezérlése" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "ElÅ‘re megadott parancsok végrehajtása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "És még sok más…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect a GNOME Shellben" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Kapcsolódás egy eszközhöz…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Mégsem" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Kapcsolódás" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP-cím" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Nincsenek névjegyek" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Súgó" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Adjon meg egy telefonszámot vagy nevet" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Egyéb" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "SMS küldése" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Küldés" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Az eszköz nincs csatlakoztatva" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Üzenet küldése" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Ãrja be az üzenetet" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Üzenet" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Ãrja be az üzenetet, majd küldéshez nyomja meg az Entert" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Üzenetküldés" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Új beszélgetés" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Nincsenek beszélgetések" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nincs kiválasztva beszélgetés" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Indítson, vagy válasszon ki egy beszélgetést" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "ÉrintÅ‘tábla.\n" "Az egérkurzor mozgatásához húzza a kurzort ezen a területen.\n" "Hosszan megnyomva húzhatja az egérkurzort.\n\n" "Egyet kattintva elküldi azt a párosított eszközre.\n" "Bal, középsÅ‘, jobb gombés, és görgetés." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Parancs szerkesztése" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Mentés" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Név" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Parancssor" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Válasszon egy programot" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Megnyitás" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Asztali gép" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "FényképezÅ‘" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Vágólap-szinkronizálás" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Médialejátszók" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Egér és billentyűzet" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "HangerÅ‘szabályzó" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Fájlok" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Fájlok fogadása" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Fájlok mentése ide" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Megosztás" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Eszköz akkumulátora" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Alacsony töltöttség értesítés" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Szabadon állítható feltöltöttségi értesítés" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Teljes töltöttség értesítés" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Rendszer-akkumulátor" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Statisztikák megosztása" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Akkumulátor" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Parancsok" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Parancs hozzáadása" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Értesítések megosztása" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Megosztás aktív használat közben" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Alkalmazások" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Értesítések" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Névjegyek" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "BejövÅ‘ hívások" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "HangerÅ‘" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Média szüneteltetése" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Folyamatban lévÅ‘ hívások" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Mikrofon némítása" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonálás" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Műveletek gyorsbillentyűi" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Összes visszaállítása…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Gyorsbillentyűk" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "BÅ‘vítmények" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Kísérleti" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Eszköz gyorsítótár" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Gyorsítótár ürítése…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Régi fajta SMS támogatás" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFPT automatikus csatolása" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Speciális" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Gyorsbillentyűk" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Eszköz beállításai" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Párosítás" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Az eszköz nincs párosítva" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Párosítás elÅ‘tt testre szabhatja az eszközt" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Titkosítási információ" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Párosítás megszűntetése" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Eszközre" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "EszközrÅ‘l" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Semmi" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Visszaállítás" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Halkítás" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Némítás" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Kiválasztás" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "A megszakításhoz nyomjon Esc gombot, vagy Backspace-t a gyorsbillentyű visszaállításához." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Eszköz neve" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Ãtnevezés" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Frissítés" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobilbeállítások" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Szervizmenü" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Eszközmenü" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Eszköz nevének szerkesztése" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Eszközök" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Eszközök keresése…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "BÅ‘vítmény beállításai" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "A GSConnect a GNOME Shell zárolt állapotában is aktív marad" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "BöngészÅ‘ bÅ‘vítmények" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Engedélyez" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Ez az eszköz nem látható a párosítatlan eszközök számára" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Felderítés letiltva" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Megjelenítési mód" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Felhasználói menü" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Naplófájl készítése terméktámogatáshoz" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "A GSConnect névjegye" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Eszköz kiválasztása" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Kiválasztás" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Nem található eszköz" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Eszközlista" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Jelentés" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Valami elromlott" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "A GSConnect egy váratlan hibába ütközött. Kérem, jelentse a hibát, és csatoljon minden olyan információt, ami segíthet." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Technikai részletek" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Küldés mobileszközre" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Szinkronizálás az eszközei között" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "Egy eszköz csatlakoztatva" msgstr[1] "%d eszköz csatlakoztatva" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Szerkesztés" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Eltávolítás" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Letiltva" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "%s módosításához adjon meg egy új gyorsbillentyűt" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s már használatban van" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Teljes KDE Connect implementáció GNOME-hoz" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Báthory Péter " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "A hibakeresési üzenetek naplózásra kerülnek. Tegye meg a hiba reprodukálásához szükséges lépést, majd nézze át a naplót." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Napló átnézése" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Okostelefon" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Táblagép" #: src/preferences/service.js:495 msgid "Television" msgstr "TV" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Nincs párosítva" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Nincs csatlakoztatva" #: src/preferences/service.js:525 msgid "Connected" msgstr "Csatlakoztatva" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Várakozás a szolgáltatásra…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Kattintson ide, hogy segítséget kapjon a hibaelhárításhoz" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "További információért kattintson ide" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Telefonszám hívása" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Fájl megosztása" #: src/service/daemon.js:355 msgid "List available devices" msgstr "ElérhetÅ‘ eszközök listázása" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Összes eszköz listázása" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Céleszköz" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Üzenet szövege" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Értesítés küldése" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Értesítés alkalmazásnév" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Értesítés törzs" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Értesítés ikon" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Értesítés ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Fotó" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Csörgetés" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Hivatkozás megosztása" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Szöveg megosztása" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Kiadás verziószámának megjelenítése" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth eszköz itt: %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "EllenÅ‘rzÅ‘ kulcs: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Párosítási kérelem innen: %s" #: src/service/device.js:850 msgid "Reject" msgstr "Elutasítás" #: src/service/device.js:855 msgid "Accept" msgstr "Elfogadás" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "A felderítés le lett tiltva a hálózaton lévÅ‘ eszközök száma miatt." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "Az OpenSSL nem található" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "A port már használatban van" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Akkumulátoradatok küldése és fogadása" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Akkumulátor feltöltve" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Teljesen feltöltve" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: az akkumulátor elérte a megadott töltöttségi szintet" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% töltöttség" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: alacsony töltöttség" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% van hátra" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Vágólap" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Vágólap tartalmának megosztása" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Vágólap küldése" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Vágólap fogadása" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "A párosított eszköz névjegyeinek elérése" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Keresd meg a telefonom" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "A párosított eszköz megcsörgetése" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Egérpad" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "A párosított eszköz egérként és billentyűzetként való használatának egedélyezése" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Távoli bevitel" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Kétirányú médialejátszó-távirányító" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Ismeretlen" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Értesítések megosztása a párosított eszközzel" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Értesítés megszakítása" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Értesítés bezárása" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Válasz értesítésre" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Értesítés aktiválása" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Párosított eszköz megkérése, hogy készítsen fényképet, és vigye át erre a számítógépre" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Sikertelen átvitel" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "„%s†küldése %s eszközre nem sikerült" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Pingelések fogadása és küldése" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Prezentáció" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "A párosított eszköz használata prezentációs távirányítóként" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Parancsok futtatása" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Parancsok futtatása a párosított eszközön, vagy elÅ‘re megadott parancsok futtassanak engedélyezése ezen a számítógépen" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "A párosított eszköz fájlrendszerének böngészése" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Csatolás" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Leválasztás" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s hibát jelzett" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Megosztás" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Fájlok és URL-ek megosztása eszközök között" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s zámára nem engedélyezett a fájlok feltöltése" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Fájl átvitele" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "„%s†fogadása innen: %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Sikeres átvitel" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "„%s†fogadva innen: %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Fájl helyének megjelenítése" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Fájl megnyitása" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "„%s†fogadása %s eszközrÅ‘l nem sikerült" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "%s által megosztott szöveg" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "„%s†küldése ide: %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "„%s†elküldve ide: %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Fájlok küldése ide: %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Megnyitás ha kész" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Hivatkozás küldése ide: %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Értesítés új SMS-ekrÅ‘l, és a párosított eszköz SMS-einek olvasása és SMS-ek küldése" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Új SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "SMS válasz" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "SMS megosztása" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "RendszerhangerÅ‘" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "A rendszerhangerÅ‘ állításának egedélyezése a párosított eszköz számára" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio nem található" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Értesítés a hívásokról és a rendszerhangerÅ‘ állítása csörgés/folyamatban lévÅ‘ hívások közben" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Hívás némítása" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Ismeretlen névjegy" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "BejövÅ‘ hívás" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Hívás folyamatban" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Munkahelyi" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Otthoni" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Küldés neki: %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Épp most" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Tegnap・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d perc" msgstr[1] "%d perc" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Nem érhetÅ‘ el" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Csoportos üzenet" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Te: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "És %d másik névjegy" msgstr[1] "És további %d" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "A távoli billentyűzet %s eszközön nem aktív" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (becslés…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d a feltöltésig)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d van hátra)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Válasz" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Hivatkozások megosztása GSConnecttel, közvetlenül a böngészÅ‘bÅ‘l vagy SMS-ben." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "A szolgáltatás nem érhetÅ‘ el" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Megnyitás böngészÅ‘ben" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/id.po000066400000000000000000001003641460766671100234220ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Indonesian\n" "Language: id_ID\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: id\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementasi KDE Connect untuk GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Tim GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Membagikan berkas, tautan, dan teks" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Mengirim serta menerima pesan" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Menyinkronkan isi papan klip" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Menyinkronkan kontak" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Menyinkronkan notifikasi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Mengendalikan volume sistem" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Dan banyak lagi…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect di GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Hubungkan dengan…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Batal" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Hubungkan" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Alamat IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Bantuan" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Ketik nomor telepon atau nama" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Lainnya" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Kirim SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Kirim" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Perangkat terputus" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Kirim Pesan" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Ketik pesan" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Ketik pesan dan tekan Enter untuk mengirim" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Perpesanan" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Percakapan Baru" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Tidak Ada Percakapan" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Tidak ada percakapan yang dipilih" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Pilih atau mulai sebuah percakapan" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Sunting Perintah" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Simpan" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nama" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Baris Perintah" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Buka" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Desktop" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Sinkronisasi Papan Klip" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Pemutar Media" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Tetikus & Papan Ketik" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Berkas-berkas" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Terima berkas-berkas" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Simpan berkas-berkas di" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Berbagi" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Baterai Perangkat" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Notifikasi Baterai Lemah" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Notifikasi Baterai Terisi Penuh" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Baterai Sistem" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Bagikan Statistik" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Baterai" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Tambahkan Perintah" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Bagikan Notifikasi" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Aplikasi-aplikasi" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notifikasi" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontak" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Panggilan Masuk" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Panggilan Aktif" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Bisukan Mikrofon" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telepon" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Pintasan Tindakan" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Pintasan" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Plugin-plugin" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Eksperimental" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Cache Perangkat" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Hapus Cache…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Lanjutan" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Pintasan Papan Ketik" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Pengaturan Perangkat" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Hubungkan" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Perangkat tidak terhubung" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Anda dapat mengkonfigurasi perangkat ini dulu sebelum menghubungkan" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informasi Enkripsi" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Putuskan" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Ke Perangkat" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Dari Perangkat" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Tidak Ada" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Pulihkan" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Kecilkan" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Bisukan" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nama Perangkat" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "Ubah _nama" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Muat ulang" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menu Layanan" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menu Perangkat" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Sunting Nama Perangkat" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Perangkat-perangkat" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Mencari perangkat…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Aktifkan" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Perangkat ini tidak terlihat pada perangkat yang belum dihubungkan" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Mode Tampilan" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Hasilkan Log Dukungan" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Tentang GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Pilih Perangkat" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Pilih" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Tidak Ada Perangkat Yang Ditemukan" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Daftar Perangkat" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Laporkan" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Ada yang salah" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect mengalami kesalahan yang tak terduga. Harap laporkan masalahnya dan sertakan informasi yang mungkin membantu." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Keterangan Teknis" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d terhubung" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Sunting" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Hapus" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Dinonaktifkan" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s sudah digunakan" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Implementasi lengkap KDE Connect untuk GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "@liimee" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "" #: src/preferences/service.js:421 msgid "Review Log" msgstr "" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Ponsel" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisi" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Tidak terhubung" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Terputus" #: src/preferences/service.js:525 msgid "Connected" msgstr "Terhubung" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Menunggu layanan…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Klik untuk informasi lebih lanjut" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Bagikan Berkas" #: src/service/daemon.js:355 msgid "List available devices" msgstr "" #: src/service/daemon.js:364 msgid "List all devices" msgstr "" #: src/service/daemon.js:373 msgid "Target Device" msgstr "" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Isi Pesan" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Kirim Notifikasi" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Isi Notifikasi" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Ikon Notifikasi" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID Notifikasi" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Dering" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Bagikan Tautan" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Bagikan Teks" #: src/service/daemon.js:532 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Permintaan Dihubungkan dari %s" #: src/service/device.js:850 msgid "Reject" msgstr "Tolak" #: src/service/device.js:855 msgid "Accept" msgstr "Terima" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL tidak ditemukan" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Port sudah digunakan" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Baterai penuh" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Terisi Penuh" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Terisi" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Baterai lemah" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% tersisa" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Papan Klip" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Bagikan isi papan klip" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Dorong Papan Klip" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Tarik Papan Klip" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Temukan Ponselku" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Tutup Notifikasi" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Balas Notifikasi" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Minta perangkat yang terhubung untuk mengambil foto dan mengirimkannya ke PC ini" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Gagal mengirim “%s†ke %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Kirim dan terima ping" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Presentasi" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Gunakan perangkat terhubung sebagai presenter" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Jalankan Perintah" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Jalankan perintah pada perangkat terhubung Anda atau biarkan perangkat tersebut menjalankan perintah yang sudah ditentukan di PC ini" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Muat" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s melaporkan sebuah kesalahan" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Bagikan" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s tidak diizinkan untuk mengunggah berkas" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Menerima “%s†dari %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "“%s†dari %s diterima" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Buka Berkas" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Gagal menerima “%s†dari %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Teks Dibagikan Oleh %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Mengirim “%s†ke %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "“%s†dikirim ke %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Kirim berkas-berkas ke %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Buka saat selesai" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Kirim tautan ke %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Kirim dan baca SMS di perangkat yang terhubung dan dapatkan notifikasi ketika menerima SMS baru" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "SMS Baru (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Balas SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volume Sistem" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio tidak ditemukan" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Panggilan masuk" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Panggilan aktif" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Faks" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Kantor" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Seluler" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Rumah" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Kirim ke %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Baru saja" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Kemarin • %s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d menit" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Tidak tersedia" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Anda: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Dan %d kontak lain" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Memperkirakan…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d:%02d Sampai Penuh)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d Tersisa)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Balas" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Layanan Tidak Tersedia" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Buka di Browser" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/it.po000066400000000000000000001060141460766671100234400ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-08-25 22:45\n" "Last-Translator: \n" "Language-Team: Italian\n" "Language: it_IT\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: it\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementazione di KDE Connect per GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Team GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect è un'implementazione completa di KDE Connect per GNOME Shell, integrata con Nautilus, Chrome e Firefox. Il team di GSConnect produce applicazioni per Linux, BSD, Android, Sailfish, iOS, macOS e Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Con GSConnect è possibile connettersi in sicurezza a dispositivi mobili e altri computer per:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Condividere file, link e testi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Inviare e ricevere messaggi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Sincronizzare il contenuto degli appunti" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Sincronizzare i contatti" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Sincronizzare le notifiche" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Controllare i contenuti multimediali" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Controllare il volume di sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Eseguire comandi predefiniti" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "E altro…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect su GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Connetti a…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Annulla" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Connetti" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Indirizzo IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Nessun contatto" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Aiuto" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Digita un numero di telefono o un nome" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Altro" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Invia SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Invia" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Il dispositivo è disconnesso" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Invia un messaggio" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Digita un messaggio" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Campo Messaggio" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Digitare un messaggio e premere Invio per inviarlo" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Messaggi" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nuova conversazione" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Nessuna conversazione" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nessuna conversazione selezionata" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Seleziona o inizia una conversazione" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Touchpad.\n" "Trascina su questa area per spostare il cursore del mouse.\n" "Premi a lungo per trascinare il cursore del mouse.\n\n" "Un semplice clic verrà inviato al dispositivo accoppiato.\n" "Sinistra, centrale, tasto destro e rotelle della ruota." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Comando Modifica" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Salva" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nome" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Linea di comando" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Seleziona un eseguibile" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Apri" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Desktop" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Fotocamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Sincronizzazione appunti" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Riproduzione multimediale" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Mouse e tastiera" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Controllo volume" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "File" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Ricezione file" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Salva i file su" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Condivisione" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Batteria del dispositivo" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Notifica di batteria scarica" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Notificare il raggiungimento del livello personalizzato di carica" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Notifica di ricarica completa" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Batteria di sistema" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Condivisione statistiche" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Batteria" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Comandi" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Aggiungere comando" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Condividi notifiche" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Condividi quando attivo" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Applicazioni" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notifiche" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contatti" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Chiamate in entrata" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Metti in pausa il media" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Chiamate in uscita" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Disattiva il microfono" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonia" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Scorciatoie azioni" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Ripristina tutte…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Scorciatoie" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Plugin" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Sperimentale" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Cache del dispositivo" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Cancella cache…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Supporto SMS legacy" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Montaggio automatico SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avanzate" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Scorciatoie da tastiera" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Impostazioni dispositivo" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Accoppia" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Il dispositivo è disaccoppiato" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "È possibile configurare questo dispositivo prima dell'accoppiamento" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informazioni di criptazione" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Disaccoppia" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Al dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Dal dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Niente" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Ripristina" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Riduci" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Silenzia" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Imposta" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Premi ESC per annullare o Backspace per ripristinare la scorciatoia." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nome del dispositivo" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Rinomina" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Aggiorna" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Impostazioni dispositivi" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menu Servizio" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menu Dispositivo" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Modifica il nome del dispositivo" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Dispositivi" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Ricerca dei dispositivi…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Impostazioni estensione" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect rimane attivo quando GNOME Shell è bloccato" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Estensioni browser" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Abilita" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Questo dispositivo è invisibile ai dispositivi disaccoppiati" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Ricerca disattiva" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Modalità di visualizzazione" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Pannello" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Menu utente" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Genera log di supporto" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Informazioni su GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Seleziona un dispositivo" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Seleziona" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Dispositivo non trovato" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Elenco dispositivi" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Segnalazione" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Qualcosa è andato storto" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect ha riscontrato un errore imprevisto. Segnalare il problema e includere le informazioni necessarie." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Dettagli tecnici" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Invia al dispositivo mobile" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Sincronizza tra i dispositivi connessi" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d connesso" msgstr[1] "%d connessi" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Modifica" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Rimuovi" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Disabilitato" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Inserisci una nuova scorciatoia per %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s già in uso" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Un'implementazione completa di KDE Connect per GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Jimmy Scionti " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "I messaggi di debug vengono registrati. Adotta tutte le misure necessarie per riprodurre un problema, quindi rivedi il registro." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Log delle Revisioni" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisione" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Disaccoppiato" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Disconnesso" #: src/preferences/service.js:525 msgid "Connected" msgstr "Connesso" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "In attesa del servizio…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Click per la risoluzione del problema" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Click per altre informazioni" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Componi numero" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Invia file" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Elenco dei dispositivi disponibili" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Elenco di tutti i dispositivi" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Dispositivo di destinazione" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Corpo del messaggio" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Invia notifica" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Nome app di notifica" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Corpo della notifica" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Icona di notifica" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID della notifica" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Squilla" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Condividi collegamento" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Invia testo" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Mostrare versione di rilascio" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositivo Bluetooth a %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Chiave di verifica: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Richiesta di accoppiamento da %s" #: src/service/device.js:850 msgid "Reject" msgstr "Rifiuta" #: src/service/device.js:855 msgid "Accept" msgstr "Accetta" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "La ricerca è stata disattivata a causa dell'elevato numero di dispositivi nella rete." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL non trovato" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Porta già in uso" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Scambiare informazioni sulla batteria" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: la batteria è carica" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Completamente carica" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: la batteria ha raggiunto il livello di carica impostato" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% carico" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batteria quasi scarica" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% rimanenti" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Appunti" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Condividere il contenuto degli appunti" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Invia appunti" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Ricevi appunti" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Accedere ai contatti del dispositivo" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Trova il mio telefono" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Fare squillare il dispositivo" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Permette di usare il dispositivo come un mouse e una tastiera" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Immissione remota" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Controllo di riproduzione multimediale bidirezionale" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Sconosciuto" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Condividere le notifiche con il dispositivo" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Cancella notifica" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Chiudi notifica" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Rispondi alla notifica" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Attivare le notifiche" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Richiedere al dispositivo di scattare una foto e trasferirla su questo PC" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Trasferimento fallito" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Invio fallito di \"%s\" a %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Inviare e ricevere ping" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Presentazione" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Usa il dispositivo come presentatore" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Esegui comandi" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Eseguire comandi sul dispositivo o lasciare che il dispositivo esegua comandi predefiniti su questo PC" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Sfogliare il file system del dispositivo" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Monta" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Smonta" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s ha segnalato un errore" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Invia file" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Condividere file e URL tra dispositivi" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s non può caricare file" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Trasferimento file" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Ricezione in corso di \"%s\" da %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Trasferimento riuscito" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Ricevuto \"%s\" da %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Mostra posizione file" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Apri file" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Ricezione fallita di \"%s\" da %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Testo condiviso da %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Invio in corso di \"%s\" a %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "\"%s\" inviato a %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Invia file a %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Apri al termine" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Invia collegamento a %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Inviare e leggere SMS del dispositivo e ricevere le notifiche per nuovi SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nuovo SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Rispondi all'SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Condividi SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volume di sistema" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Permettere al dispositivo di controllare il volume del sistema" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio non trovato" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Ricevere notifiche sulle telefonate e livellare il volume durante le telefonate" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silenzia la chiamata in arrivo" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Contatto sconosciuto" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Chiamata in arrivo" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Chiamata in corso" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Lavoro" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobile" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Casa" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Invia a %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Proprio ora" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Ieri・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minuti" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Non disponibile" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Messaggio di gruppo" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Tu: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "E %d altro contatto" msgstr[1] "E altri %d" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "La tastiera remota su %s non è attiva" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Stima…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d Al completamento)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d Rimanenti)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Rispondi" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Condividi collegamenti con GSConnect, direttamente nel browser o via SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Servizio non disponibile" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Apri nel browser" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/ko.po000066400000000000000000001050511460766671100234350ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Korean\n" "Language: ko_KR\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: ko\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "GNOMEì„ ìœ„í•œ KDE Connectì˜ í˜¸í™˜ 기능" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect 팀" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect는 노틸러스, í¬ë¡¬, 파ì´ì–´í­ìФ ì—°ë™ê³¼ 함께 사용하는 그놈 ì…¸ì„ ìœ„í•œ 완전한 KDE Connect 구현체 입니다. KDE Connect 팀 ì—서는 리눅스, BSD, 안드로ì´ë“œ, Sailfish, iOS, macOS ë° Windows ìš© ì•±ì„ ì œê³µí•˜ê³  있습니다." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "GSConnect로 ëª¨ë°”ì¼ ê¸°ê¸°ì™€ 다른 ë°ìФí¬íƒ‘ì„ ì•ˆì „í•˜ê²Œ 연결하기:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "파ì¼ê³¼ ë§í¬, í…스트 ë“±ì„ ê³µìœ í•©ë‹ˆë‹¤" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "메시지를 주고 받기" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "í´ë¦½ë³´ë“œ ë‚´ì—­ ë™ê¸°í™”" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "ì—°ë½ì²˜ ë™ê¸°í™”" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "알림 ë™ê¸°í™”" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "미디어 플레ì´ì–´ 제어" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "시스템 볼륨 제어" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "ì •ì˜ëœ 명령 실행" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "ë” ë³´ê¸°â€¦" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GNOME Shellì˜ GSConnect" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "ì—°ê²°" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "취소" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "ì—°ê²°" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP 주소" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "ì—°ë½ì²˜ ì—†ìŒ" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "ë„움ë§" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "전화번호나 ì´ë¦„ì„ ìž…ë ¥" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "기타" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "메시지 보내기" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "보내기" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "디바ì´ìŠ¤ì˜ ì—°ê²°ì´ ëŠê²¼ìŠµë‹ˆë‹¤" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "메시지 보내기" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "메시지 ìž…ë ¥" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "메시지 목ë¡" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "메시지를 입력한 후 전송하려면 Enter 키를 누르십시오" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "메시지" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "새 대화" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "대화 ì—†ìŒ" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "ì„ íƒëœ 대화 ì—†ìŒ" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "대화를 ì„ íƒí•˜ê±°ë‚˜ 시작" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "터치패드.\n" "마우스 커서를 움ì§ì´ë ¤ë©´ ì´ ì˜ì—­ì„ 드래그 하십시오.\n" "마우스 커서 드래그를 하려면 길게 누른 후 드래그 하십시오.\n\n" "왼쪽 í´ë¦­, 중간 í´ë¦­, 오른쪽 í´ë¦­, 휠 스í¬ë¡¤ 등.\n" "간단한 í´ë¦­ 신호가 íŽ˜ì–´ë§ ëœ ìž¥ì¹˜ì— ì „ë‹¬ë©ë‹ˆë‹¤." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "명령 편집" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "저장" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "ì´ë¦„" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "커맨드 ë¼ì¸" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "실행할 íŒŒì¼ ì„ íƒ" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "열기" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "ë°ìФí¬í†±" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "ì¹´ë©”ë¼" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "í´ë¦½ë³´ë“œ ë™ê¸°í™”" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "미디어 플레ì´ì–´" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "마우스와 키보드" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "ìŒëŸ‰ 제어" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "파ì¼" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "íŒŒì¼ ë°›ê¸°" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "파ì¼ë¡œ 저장" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "공유 중" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "장치 배터리" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "배터리 부족 알림" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "ì¶©ì „ 완료 알림" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "시스템 배터리" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "통계 공유" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "배터리" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "명령" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "명령 추가" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "알림 공유" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "í™œì„±í™”ë  ë•Œ 공유" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "프로그램" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "알림" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "ì—°ë½ì²˜" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "수신 ì „í™”" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "볼륨" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "미디어 ì¼ì‹œì •ì§€" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "발신 ì „í™”" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "마ì´í¬ ìŒì†Œê±°" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "ì „í™”" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "ë™ìž‘ 바로가기" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "모든 설정 초기화" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "바로가기" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "플러그ì¸" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "ì‹¤í—˜ì  ê¸°ëŠ¥" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "장치 ìºì‹œ" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "ìºì‹œ ì‚­ì œ" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "레거시 SMS ì§€ì›" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFTP ìžë™ 마운트" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "고급" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "단축 키" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "장치 설정" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "페어ë§" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "디바ì´ìŠ¤ì˜ íŽ˜ì–´ë§ì´ í•´ì œë˜ì—ˆìŠµë‹ˆë‹¤" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "íŽ˜ì–´ë§ ì „ì— ë””ë°”ì´ìŠ¤ë¥¼ 설정해야 합니다" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "암호화 ì •ë³´" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "íŽ˜ì–´ë§ í•´ì œ" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "ë‚´ 기기로 전송" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "ë‚´ 기기ì—서 받아오기" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "ì—†ìŒ" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "ë³µì›" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "낮게" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "무ìŒ" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "설정" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Esc를 눌러 취소하거나 백스페ì´ìФ 키를 눌러 단축 키를 초기화합니다." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "디바ì´ìФ ì´ë¦„" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "ì´ë¦„ 변경" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "새로고침" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "ëª¨ë°”ì¼ ì„¤ì •" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "서비스 메뉴" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "장치 메뉴" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "장치 ì´ë¦„ 바꾸기" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "장치" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "장치를 찾는 중…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "브ë¼ìš°ì € 확장 기능" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "사용" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "ì´ ê¸°ê¸°ëŠ” 페어ë§ë˜ì§€ 않았으므로 ë³´ì´ì§€ 않습니다" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Discovery 비활성화" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "보기 모드" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "패ë„" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "ì‚¬ìš©ìž ë©”ë‰´" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "ì§€ì› ë¡œê·¸ ìƒì„±" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "GSConnect ì •ë³´" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "장치 ì„ íƒ" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "ì„ íƒ" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "장치를 ì°¾ì„ ìˆ˜ ì—†ìŒ" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "장치 목ë¡" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "기ë¡" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "무언가가 ìž˜ëª»ëœ ê±° 같습니다" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnectì— ì˜ˆìƒì¹˜ 못한 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "ìžì„¸ížˆ 보기" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "ëª¨ë°”ì¼ ê¸°ê¸°ë¡œ 전송" #: src/extension.js:52 msgid "Sync between your devices" msgstr "기기 ë™ê¸°í™”" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d ì—°ê²°ë¨" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "편집" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "제거" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "사용 안 함" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "%s 바로가기를 대체할 새로운 바로가기를 입력하십시오" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s(ì€)는 ì´ë¯¸ 사용 중입니다" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "ê·¸ë†ˆì„ ìœ„í•œ KDE Connectì˜ í˜¸í™˜ 기능" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "UtsushimiNeneka(네네카)" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "디버그 메시지가 ë¡œê·¸ì— ê¸°ë¡ë©ë‹ˆë‹¤." #: src/preferences/service.js:421 msgid "Review Log" msgstr "로그 확ì¸" #: src/preferences/service.js:489 msgid "Laptop" msgstr "노트ë¶" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "스마트í°" #: src/preferences/service.js:493 msgid "Tablet" msgstr "태블릿" #: src/preferences/service.js:495 msgid "Television" msgstr "TV" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "íŽ˜ì–´ë§ í•´ì œë¨" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "ì—°ê²°ë˜ì§€ 않ìŒ" #: src/preferences/service.js:525 msgid "Connected" msgstr "ì—°ê²°ë¨" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "서비스를 기다리는 중" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "í´ë¦­í•˜ì—¬ 문제해결 ë„움 받기" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "ìžì„¸í•œ ë‚´ìš©ì„ ë³´ë ¤ë©´ 여기를 í´ë¦­í•˜ì‹­ì‹œì˜¤." #: src/service/daemon.js:298 msgid "Dial Number" msgstr "전화번호" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "íŒŒì¼ ê³µìœ " #: src/service/daemon.js:355 msgid "List available devices" msgstr "사용 가능한 장치 목ë¡" #: src/service/daemon.js:364 msgid "List all devices" msgstr "모든 장치 보기" #: src/service/daemon.js:373 msgid "Target Device" msgstr "ëŒ€ìƒ ê¸°ê¸°" #: src/service/daemon.js:415 msgid "Message Body" msgstr "메시지 ë‚´ìš©" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "알림 보내기" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "알림 앱 ì´ë¦„" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "알림 ë‚´ìš©" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "알림 ì•„ì´ì½˜" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "알림 ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "사진" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "í•‘" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "벨" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "ë§í¬ 공유" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "í…스트 공유" #: src/service/daemon.js:532 msgid "Show release version" msgstr "릴리즈 버전 보기" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "ì¸ì¦ 키: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "%s(으)로부터 ì—°ê²° 요청ë¨" #: src/service/device.js:850 msgid "Reject" msgstr "ê±°ë¶€" #: src/service/device.js:855 msgid "Accept" msgstr "ë™ì˜" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSLì„ ì°¾ì„ ìˆ˜ ì—†ìŒ" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "í¬íŠ¸ê°€ 사용 중입니다" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "배터리 ì •ë³´ êµí™˜" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: ì¶©ì „ì´ ì™„ë£Œë˜ì—ˆìŠµë‹ˆë‹¤" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "ì¶©ì „ 완료" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% ì¶©ì „ë¨" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: 배터리가 부족합니다" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% 남ìŒ" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "í´ë¦½ë³´ë“œ" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "í´ë¦½ë³´ë“œ ë‚´ìš© ë™ê¸°í™”" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "í´ë¦½ë³´ë“œ 보내기" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "í´ë¦½ë³´ë“œ 받기" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "페어ë§ëœ ìž¥ì¹˜ì˜ ì—°ë½ì²˜ 보기" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "ë‚´ 휴대전화 찾기" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "페어ë§ëœ 장치 벨소리 울리기" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "트랙패드" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "페어ë§í•œ 장치를 ì›ê²© 마우스 ë° í‚¤ë³´ë“œë¡œ 사용할 수 있ë„ë¡ í•¨" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "ì›ê²© ìž…ë ¥" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "알 수 ì—†ìŒ" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "페어ë§í•œ 장치와 알림 공유" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "알림 취소" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "알림 닫기" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "알림 답장" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "알림 활성화" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "페어ë§í•œ ìž¥ì¹˜ì— ì‚¬ì§„ì„ ì°ì–´ì„œ ì´ PC로 ë³´ë‚´ë„ë¡ ìš”ì²­" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "전송 실패" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "\"%s\"를 %sì— ë³´ë‚´ëŠ” ë° ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤." #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "í•‘ 주고 받기" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "ì—°ë½: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "프레젠테ì´ì…˜" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "페어ë§í•œ 장치를 프레젠터로 사용" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "명령 실행" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "페어ë§í•œ 장치 파ì¼ì‹œìŠ¤í…œ íƒìƒ‰" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "마운트" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "마운트 í•´ì œ" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "공유" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "장치간 파ì¼ê³¼ URL 공유" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s로 파ì¼ì„ 올릴 수 없습니다." #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "íŒŒì¼ ì „ì†¡ 중" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "\"%s\"를 %sì—서 받는 중" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "전송 성공" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "\"%s\"를 %sì—서 받았습니다." #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "íŒŒì¼ ìœ„ì¹˜ 표시" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "íŒŒì¼ ì—´ê¸°" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "\"%s\"를 %sì—서 받아오는 ë° ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤." #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "%s(으)로부터 í…스트가 공유ë¨" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "\"%s\" 파ì¼ì„ %s(으)로 보내는 중" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "\"%s\" 파ì¼ì„ %s(으)로 보냈습니다." #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "%s로 íŒŒì¼ ë³´ë‚´ê¸°" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "완료ë˜ë©´ 열기" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "%s로 ë§í¬ 보내기" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "메시지" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "페어ë§í•œ 장치로 SMS를 ì½ê±°ë‚˜ ë³´ë‚´ê³  새 SMSê°€ 오면 알림받기" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "새 메시지" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "메시지 답장" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "메시지 공유" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "시스템 볼륨" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "페어ë§ëœ ìž¥ì¹˜ì˜ ì‹œìŠ¤í…œ 볼륨 제어 활성화" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio를 ì°¾ì„ ìˆ˜ ì—†ìŒ" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "ì „í™” 받거나 걸 때 전화나 시스템 볼륨 ì¡°ì • 알림 받기" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "ë¬´ìŒ í†µí™”" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "알 수 없는 ì—°ë½ì²˜" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "수신 ì „í™”" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "발신 ì „í™”" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "팩스" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "ì§ìž¥" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "휴대전화" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "홈" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "%s로 보내기" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "방금" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "ì–´ì œ %s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%dë¶„" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "사용할 수 ì—†ìŒ" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "그룹 메시지" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "%s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "외 %d명" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "%sì˜ ì›ê²© 키보드가 활성화ë˜ì§€ 않ìŒ" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (측정 중…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (ì¶©ì „ë  ë•Œê¹Œì§€ %d : %02d)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d : %02d 남ìŒ)" #: src/shell/notification.js:58 msgid "Reply" msgstr "답장" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "GSConnect로 ë§í¬ë¥¼ 공유하여 웹 브ë¼ìš°ì €ì—서 열거나 메시지를 통해 공유합니다." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "서비스 안 ë¨" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "웹 브ë¼ìš°ì €ì—서 열기" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/lt.po000066400000000000000000001070411460766671100234440ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-08-26 23:10\n" "Last-Translator: \n" "Language-Team: Lithuanian\n" "Language: lt_LT\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%10==1 && (n%100>19 || n%100<11) ? 0 : (n%10>=2 && n%10<=9) && (n%100>19 || n%100<11) ? 1 : n%1!=0 ? 2: 3);\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: lt\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "KDE Connect įgyvendinimas, skirtas GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect komanda" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect yra pilnas KDE Connect įgyvendinimas, ypatingai skirtas integracijai su GNOME Shell ir Nautilus, Chrome bei Firefox. KDE Connect komanda turi programas, skirtas Linux, BSD, Android, Sailfish, iOS, macOS ir Windows sistemoms." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Naudodami GSConnect galite saugiai prisijungti prie mobiliųjų įrenginių ir kitų stalinių kompiuterių norÄ—dami:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Bendrinti failus, nuorodas ir tekstÄ…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Siųsti ir gauti žinutes" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Sinchronizuoti iÅ¡karpinÄ—s turinį" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Sinchronizuoti adresatus" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Sinchronizuoti praneÅ¡imus" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Valdyti medijos leistuves" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Valdyti sistemos garsį" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Vykdyti iÅ¡ anksto nustatytas komandas" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Ir daugiau…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect kartu su GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Prisijungti prie…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Atsisakyti" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Prisijungti" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP adresas" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Adresatų nÄ—ra" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Žinynas" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Ä®raÅ¡ykite telefono numerį ar vardÄ…" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Kitas" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Siųsti SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Siųsti" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Ä®renginys yra atsijungÄ™s" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Siųsti žinutÄ™" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "RaÅ¡yti žinutÄ™" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "ŽinutÄ—s įvedimas" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "RaÅ¡ykite žinutÄ™ ir norÄ—dami iÅ¡siųsti paspauskite „Enter“" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "SusiraÅ¡inÄ—jimai" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Naujas pokalbis" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Pokalbių nÄ—ra" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nepasirinktas joks pokalbis" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Pasirinkite arba pradÄ—kite pokalbį" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Taisyti komandÄ…" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Ä®raÅ¡yti" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Pavadinimas" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Komandų eilutÄ—" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Pasirinkti vykdomÄ…jį failÄ…" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Atverti" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Stalinis kompiuteris" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "IÅ¡karpinÄ—s sinchronizavimas" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Medijos leistuvÄ—s" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "PelÄ— ir klaviatÅ«ra" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Garsumo reguliavimas" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Failai" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Gauti failus" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Ä®raÅ¡yti failus į" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Bendrinimas" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Ä®renginio baterija" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "PraneÅ¡imas apie žemÄ… baterijos įkrovos lygį" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "PraneÅ¡imas apie įkrovimÄ… iki tinkinto lygio" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "PraneÅ¡imas apie pilnai įkrauta baterijÄ…" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Sistemos baterija" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Bendrinti statistikÄ…" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Baterija" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Komandos" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "PridÄ—ti komandÄ…" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Bendrinimo praneÅ¡imai" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Bendrinti, kai aktyvus" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Programos" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "PraneÅ¡imai" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Adresatai" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Gaunami skambuÄiai" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Garsumas" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Pristabdyti medijÄ…" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Vykstantys skambuÄiai" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Nutildyti mikrofonÄ…" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonija" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Veiksmų trumpiniai" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Atstatyti visus…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Trumpiniai" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Ä®skiepiai" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Eksperimentinis" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Ä®renginio podÄ—lis" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "IÅ¡valyti podÄ—lį…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Pasenusių SMS palaikymas" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Automatinis SFTP prijungimas" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "IÅ¡plÄ—stiniai" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "KlaviatÅ«ros trumpiniai" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Ä®renginio nustatymai" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Suporuoti" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Ä®renginys yra nesuporuotas" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "PrieÅ¡ suporuodami, galite konfigÅ«ruoti šį įrenginį" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Å ifravimo informacija" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Panaikinti suporavimÄ…" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Ä® įrenginį" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "IÅ¡ įrenginio" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Nieko" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Atkurti" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Sumažinti" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Nutildyti" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Nustatyti" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "NorÄ—dami atsisakyti, paspauskite Grįžimo (Esc) klavišą arba Naikinimo (Backspace) klavišą, norÄ—dami atstatyti trumpinį." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Ä®renginio pavadinimas" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "Pe_rvadinti" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Ä®kelti iÅ¡ naujo" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobiliųjų nustatymai" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Paslaugos meniu" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Ä®renginio meniu" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Taisyti įrenginio pavadinimÄ…" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Ä®renginiai" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "IeÅ¡koma įrenginių…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "PlÄ—tinio nustatymai" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect lieka aktyvus, kai GNOME Shell yra užrakintas" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "NarÅ¡yklių priedai" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Ä®jungti" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Å is įrenginys yra nematomas nesuporuotiems įrenginiams" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Aptikimas iÅ¡jungtas" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Rodinio veiksena" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Skydelis" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Naudotojo meniu" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Generuoti palaikymo žurnalÄ…" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Apie GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Pasirinkti įrenginį" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Pasirinkti" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Nerasta jokių įrenginių" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Ä®renginių sÄ…raÅ¡as" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Ataskaita" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Kažkas nutiko" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect susidÅ«rÄ— su netikÄ—ta klaida. PraneÅ¡kite apie problemÄ… ir įtraukite bet kokiÄ… informacijÄ…, kuri galÄ—tų padÄ—ti." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "TechninÄ— informacija" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Siųsti į mobilųjį įrenginį" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Sinchronizuoti tarp įrenginių" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d prijungtas" msgstr[1] "%d prijungti" msgstr[2] "%d prijungtų" msgstr[3] "%d prijungtas" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Taisyti" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Å alinti" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "IÅ¡jungta" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "NorÄ—dami pakeisti %s, įveskite naujÄ… trumpinį" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s jau yra naudojamas" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Pilnas KDE Connect įgyvendinimas, skirtas GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Moo" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Derinimo praneÅ¡imai yra registruojami. Atlikite reikiamus veiksmus, kad pakartotumÄ—te problemÄ…, o tuomet peržiÅ«rÄ—kite žurnalÄ…." #: src/preferences/service.js:421 msgid "Review Log" msgstr "PeržiÅ«rÄ—ti žurnalÄ…" #: src/preferences/service.js:489 msgid "Laptop" msgstr "NeÅ¡iojamas kompiuteris" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "IÅ¡manusis telefonas" #: src/preferences/service.js:493 msgid "Tablet" msgstr "PlanÅ¡etÄ—" #: src/preferences/service.js:495 msgid "Television" msgstr "Televizija" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Nesuporuotas" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Atjungtas" #: src/preferences/service.js:525 msgid "Connected" msgstr "Prijungtas" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Laukiama paslaugos…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "SpustelÄ—kite, norÄ—dami Å¡alinti nesklandumus" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "SpustelÄ—kite iÅ¡samesnei informacijai" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Rinkti numerį" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Bendrinti failÄ…" #: src/service/daemon.js:355 msgid "List available devices" msgstr "IÅ¡vardyti prieinamus įrenginius" #: src/service/daemon.js:364 msgid "List all devices" msgstr "IÅ¡vardyti visus įrenginius" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Paskirties įrenginys" #: src/service/daemon.js:415 msgid "Message Body" msgstr "PagrindinÄ— žinutÄ—s dalis" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Siųsti praneÅ¡imÄ…" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "PraneÅ¡imo programÄ—lÄ—s pavadinimas" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "PagrindinÄ— praneÅ¡imo dalis" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "PraneÅ¡imo piktograma" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "PraneÅ¡imo ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Nuotrauka" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "RyÅ¡io patikrinimas" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Rasti telefonÄ…" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Bendrinti nuorodÄ…" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Bendrinti tekstÄ…" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Rodyti laidos versijÄ…" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth įrenginys ties %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Patvirtinimo raktas: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Suporavimo užklausa nuo %s" #: src/service/device.js:850 msgid "Reject" msgstr "Atmesti" #: src/service/device.js:855 msgid "Accept" msgstr "Priimti" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Aptikimas buvo iÅ¡jungtas dÄ—l Å¡iame tinkle esanÄių įrenginių skaiÄiaus." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL nerasta" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Prievadas jau naudojamas" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Apsikeitimas informacija apie baterijÄ…" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Baterija pilna" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Pilnai įkrauta" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Baterija pasiekÄ— tinkintÄ… įkrovos lygį" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% įkrauta" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Baterija baigia iÅ¡sikrauti" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "Liko %d%%" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "IÅ¡karpinÄ—" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Bendrinti iÅ¡karpinÄ—s turinį" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "IÅ¡siųsti iÅ¡karpinÄ—s turinį" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Gauti iÅ¡kaprinÄ—s turinį" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Gauti prieigÄ… prie suporuotų įrenginių adresatų" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Rasti mano telefonÄ…" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Skambinti į suporuotÄ… įrenginį" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Jutiklinis kilimÄ—lis" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Leidžia suporuotam įrenginiui veikti kaip nuotolinei pelei ir klaviatÅ«rai" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "NuotolinÄ— įvestis" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Abikryptis nuotolinÄ—s medijos atkÅ«rimo valdymas" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Nežinoma" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Bendrinti praneÅ¡imus su suporuotu įrenginiu" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Atsisakyti praneÅ¡imo" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Užverti praneÅ¡imÄ…" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Atsakyti į praneÅ¡imÄ…" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Aktyvuoti praneÅ¡imÄ…" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "PraÅ¡yti, kad suporuotas įrenginys padarytų nuotraukÄ… ir perduotų jÄ… į šį kompiuterį" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Persiuntimas nepavyko" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Nepavyko iÅ¡siųsti “%s†į %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Siųsti ir gauti ryÅ¡io patikrinimus" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "RyÅ¡io patikrinimas: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Pristatymas" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Naudoti suporuotÄ… įrenginį kaip pristatytojÄ…" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Vykdyti komandas" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Vykdyti komandas suporuotame įrenginyje arba leisti įrenginiui vykdyti Å¡iame kompiuteryje iÅ¡ anksto apibrėžtas komandas" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "NarÅ¡yti suporuoto įrenginio failų sistemÄ…" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Prijungti" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Atjungti" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s pranešė apie klaidÄ…" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Bendrinti" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Bendrinti failus ir URL adresus tarp įrenginių" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s neleidžiama įkelti failų" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "PersiunÄiamas failas" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Gaunama „%s“ iÅ¡ %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Persiuntimas sÄ—kmingas" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Gautas „%s“ iÅ¡ %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Rodyti failo vietÄ…" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Atverti failÄ…" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Nepavyko gauti “%s†iÅ¡ %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "%s bendrino tekstÄ…" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "SiunÄiama „%s“ į %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "IÅ¡siųsta „%s“ į %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Siųsti failus į %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Užbaigus, atverti failÄ…" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Siųsti nuorodÄ… į %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Siųsti bei gauti suporuoto įrenginio SMS žinutes ir gauti praneÅ¡imus apie naujas SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nauja SMS žinutÄ— (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Atsakyti į SMS žinutÄ™" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Bendrinti SMS žinutÄ™" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Sistemos garsumas" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Leisti suporuotam įrenginiui valdyti sistemos garsumÄ…" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio nerasta" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Gauti praneÅ¡imus apie skambuÄius ir reguliuoti sistemos garsumÄ… skambuÄių metu" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Nutildyti skambutį" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Nežinomas adresatas" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Gaunamas skambutis" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Vykstantis skambutis" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Faksas" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Darbo" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobilusis" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Namų" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Siųsti %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "KÄ… tik" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Vakar・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minutÄ—" msgstr[1] "%d minutÄ—s" msgstr[2] "%d minuÄių" msgstr[3] "%d minutÄ—" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Neprieinamas" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "GrupÄ—s žinutÄ—" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "JÅ«s: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Ir dar %d adresatas" msgstr[1] "Ir dar %d adresatai" msgstr[2] "Ir dar %d adresatų" msgstr[3] "Ir dar %d adresatas" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "NuotolinÄ— klaviatÅ«ra ties %s nÄ—ra aktyvi" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (ApskaiÄiuojama…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d iki pilnos)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Liko %d∶%02d)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Atsakyti" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Bendrinti nuorodas, naudojant GSConnect, tiesiogiai į narÅ¡yklÄ™ ar per SMS žinutÄ™." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Paslauga neprieinama" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Atverti narÅ¡yklÄ—je" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/meson.build000066400000000000000000000003471460766671100246300ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # build translations in LINGUAS i18n.gettext( 'org.gnome.Shell.Extensions.GSConnect', preset: 'glib' ) GSConnect-gnome-shell-extension-gsconnect-43258f9/po/nl.po000066400000000000000000001130111460766671100234300ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-22 10:01+0530\n" "PO-Revision-Date: 2021-10-04 16:59+0200\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: Dutch \n" "Language: nl_NL\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: Poedit 3.0\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "KDE Connect-implementatie voor GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect-team" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 #, fuzzy msgid "" "GSConnect is a complete implementation of KDE Connect especially for GNOME " "Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team " "has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" "GSConnect is een volledige implementatie van KDE Connect voor GNOME Shell, " "met Nautilus-, Chrome- en Firefox-integratie. Het KDE Connect-team biedt " "clients aan voor Linux, BSD, Android, Sailfish OS, macOS en Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "" "With GSConnect you can securely connect to mobile devices and other desktops " "to:" msgstr "" "Met GSConnect kun je veilig verbinden met mobiele apparaten en computers om:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Bestanden, links en tekst te delen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Berichten te versturen en ontvangen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "De klembordinhoud te synchroniseren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Contactpersonen te synchroniseren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Meldingen te synchroniseren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Mediaspelers te bedienen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Het volumeniveau van het systeem aan te passen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Opgegeven opdrachten uit te voeren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "En nog veel meer…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:127 msgid "GSConnect in GNOME Shell" msgstr "GSConnect in GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Verbinden met…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Annuleren" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Verbinden" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP-adres" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Contactpersonen" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Hulp" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Typ een telefoonnummer of naam" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Overig" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "SMS versturen" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Versturen" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Apparaat is niet verbonden" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Bericht versturen" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Typ een bericht" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Berichtinhoud" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Typ een bericht en druk op enter om te versturen" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Berichten" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nieuw gesprek" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Geen gesprekken" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Geen gesprek gekozen" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Kies of begin een gesprek" #: data/ui/mousepad-input-dialog.ui:97 msgid "" "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n" "\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Opdracht aanpassen" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Opslaan" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Naam" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Opdrachtregel" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Kies een uitvoerbaar bestand" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Openen" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Computer" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Camera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Klembordsynchronisatie" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Mediaspelers" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Muis en toetsenbord" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Volumebeheer" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Bestanden" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Bestanden ontvangen" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Bestanden opslaan in" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Delen" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Apparaataccu" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Melding bij laag accuniveau" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Melding bij ingesteld accuniveau" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Melding bij 100% accuniveau" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Systeemaccu" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Statistieken delen" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Accu" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Opdrachten" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Opdracht toevoegen" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Meldingen omtrent delen" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Delen indien actief" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Toepassingen" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Meldingen" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contactpersonen" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Inkomende oproepen" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Media onderbreken" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Actieve gesprekken" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Microfoon dempen" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefoon" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Actiesneltoetsen" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Standaardwaarden…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Sneltoetsen" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Plug-ins" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimenteel" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Apparaatcache" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Cache legen…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Verouderde sms-ondersteuning" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFTP automatisch aankoppelen" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Geavanceerd" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Sneltoetsen" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Apparaatinstellingen" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Koppelen" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Apparaat is niet gekoppeld" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Je kunt dit apparaat instellen alvorens het te koppelen" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Versleutelingsinformatie" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Ontkoppelen" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Naar apparaat" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Van apparaat" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Niets" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Herstellen" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Verlagen" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Dempen" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Instellen" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" "Druk op Esc om te annuleren of Backspace om de standaard sneltoets te " "herstellen." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Apparaatnaam" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Naam wijzigen" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Verversen" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobiele instellingen" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Dienstmenu" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Apparaatmenu" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Apparaatnaam wijzigen" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Apparaten" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Bezig met zoeken naar apparaten…" #: data/ui/preferences-window.ui:353 msgid "Behavior When Locked" msgstr "" #: data/ui/preferences-window.ui:378 msgid "Keep Alive" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Browseradd-ons" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Inschakelen" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Dit apparaat is onzichtbaar voor niet-gekoppelde apparaten" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Herkenning uitgeschakeld" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Weergavemodus" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Paneel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Gebruikersmenu" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Ondersteuningslogboek samenstellen" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Over GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Kies een apparaat" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Kiezen" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Geen apparaat gevonden" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Apparatenlijst" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Melden" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Er is iets misgegaan" #: data/ui/service-error-dialog.ui:91 msgid "" "GSConnect encountered an unexpected error. Please report the problem and " "include any information that may help." msgstr "" "Er is een onverwachte fout opgetreden. Meld dit probleem aan de ontwikkelaar " "en beschrijf wat je deed ten tijde van de fout." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Technische gegevens" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Versturen naar mobiel apparaat" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "Verbonden" msgstr[1] "Verbonden" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Bewerken" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Verwijderen" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Uitgeschakeld" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Druk op een nieuwe sneltoets voor %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s is al in gebruik" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Een volledige KDE Connect-implementatie voor GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Heimen Stoffels " #: src/preferences/service.js:418 msgid "" "Debug messages are being logged. Take any steps necessary to reproduce a " "problem then review the log." msgstr "" "Foutopsporingsberichten worden gelogd. Doe alles wat nodig is om het " "probleem aan te tonen en kijk dan het logboek na." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Logboek nakijken" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisie" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Ontkoppeld" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Niet verbonden" #: src/preferences/service.js:525 msgid "Connected" msgstr "Verbonden" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Bezig met wachten op dienst…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Klik voor probleemoplossing" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Klik voor meer informatie" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Nummer bellen" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Bestand delen" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Beschikbare apparaten tonen" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Alle apparaten tonen" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Doelapparaat" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Berichtinhoud" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Melding versturen" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Appnaam op melding" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Meldingsinhoud" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Meldingspictogram" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Meldingsid" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Over laten gaan" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Link delen" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Tekst delen" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Uitgaveversie tonen" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetoothapparaat op %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, fuzzy, javascript-format msgid "Verification key: %s" msgstr "Meldingen" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Koppelverzoek van %s" #: src/service/device.js:850 msgid "Reject" msgstr "Weigeren" #: src/service/device.js:855 msgid "Accept" msgstr "Accepteren" #: src/service/manager.js:118 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" "Herkenning is uitgeschakeld vanwege het aantal apparaten op dit netwerk." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL is niet aangetroffen" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Deze poort is al in gebruik" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Accu-informatie uitwisselen" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: de accu is opgeladen" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Volledig opgeladen" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: de accu heeft het ingestelde oplaadniveau bereikt" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% opgeladen" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: het accuniveau is laag" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% resterend" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Klembord" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Klembordinhoud delen" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Klembord versturen" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Klembord ophalen" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Toegang tot contactpersonen van het gekoppelde apparaat" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Zoek mijn telefoon" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Gekoppeld apparaat laten rinkelen" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Touchpad" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Laat het gekoppelde apparaat fungeren als externe muis en toetsenbord" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Media twee kanten op bedienen" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Onbekend" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Meldingen delen met het gekoppelde apparaat" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Melding annuleren" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Melding sluiten" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Antwoordmelding" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Melding inschakelen" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" "Verzoek het gekoppelde apparaat een foto te maken en deze naar de pc te " "sturen" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Overdracht mislukt" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Versturen van “%s†naar %s mislukt" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Pings ontvangen en versturen" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Presentatie" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Gekoppeld apparaat laten fungeren als presentatiescherm" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Opdrachten uitvoeren" #: src/service/plugins/runcommand.js:17 msgid "" "Run commands on your paired device or let the device run predefined commands " "on this PC" msgstr "" "Voer opdrachten uit op het gekoppelde apparaat of laat het apparaat vooraf " "ingestelde opdrachten uitvoeren op deze pc" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Bladeren door het bestandssysteem van het gekoppelde apparaat" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Aankoppelen" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Ontkoppelen" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s: er is een fout opgetreden" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Delen" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Bestanden en url's uitwisselen tussen apparaten" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s mag geen bestanden sturen" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Bestandsoverdracht" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Bezig met ontvangen van “%s†van %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Overdracht voltooid" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "“%s†ontvangen van %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Bestand openen" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Ontvangen van “%s†van %s mislukt" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Tekst van %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Bezig met versturen van “%s†naar %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "“%s†is verstuurd naar %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Bestanden versturen naar %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Openen na overdracht" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Link versturen naar %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" "Lees en verstuur sms'jes van het gekoppelde apparaat en ontvang sms-meldingen" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nieuwe sms (uri)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "SMS beantwoorden" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "SMS delen" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volumeniveau van systeem" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Laat het gekoppelde apparaat het systeemvolume aanpassen" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio is niet aangetroffen" #: src/service/plugins/telephony.js:18 msgid "" "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" "Ontvang meldingen omtrent oproepen en volume-aanpassingen tijdens rinkelen/" "actieve gesprekken" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Oproep dempen" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Onbekende contactpersoon" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Inkomende oproep" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Actief gesprek" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Werk" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobiel" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Thuis" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Versturen naar %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Zojuist" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Gisteren - %s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuut" msgstr[1] "%d minuten" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Niet beschikbaar" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Groepsbericht" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Ik: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "En %d andere contactpersoon" msgstr[1] "En %d anderen" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Het externe toetsenbord op '%s' is inactief" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (bezig met berekenen…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d:%02d tot volledig opgeladen)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d resterend)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Beantwoorden" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Deel links met GSConnect, direct naar de browser of via sms." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Dienst niet beschikbaar" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Openen in browser" #~ msgid "On" #~ msgstr "Aan" #~ msgid "Off" #~ msgstr "Uit" #~ msgid "Network Error" #~ msgstr "Netwerkfout" #~ msgid "All files" #~ msgstr "Alle bestanden" #~ msgid "Camera pictures" #~ msgstr "Camera-afbeeldingen" #~ msgid "Add" #~ msgstr "Toevoegen" #~ msgid "Set Shortcut" #~ msgstr "Sneltoets instellen" #~ msgid "Authentication Failure" #~ msgstr "Authenticatiefout" #~ msgid "Keyboard not ready" #~ msgstr "Toetsenbord niet gereed" #~ msgid "%d hour" #~ msgid_plural "%d hours" #~ msgstr[0] "%d uur" #~ msgstr[1] "%d uur" #~ msgid "Until %s (%s)" #~ msgstr "Tot %s (%s)" #~ msgid "Do Not Disturb" #~ msgstr "Niet storen" #~ msgid "Until you turn off Do Not Disturb" #~ msgstr "Totdat je Niet storen uitschakelt" #~ msgid "Done" #~ msgstr "Klaar" #~ msgid "Bluetooth Device" #~ msgstr "Bluetooth-apparaat" #~ msgid "Command Shortcuts" #~ msgstr "Opdracht-sneltoetsen" #~ msgid "Delete" #~ msgstr "Verwijderen" #~ msgid "Delete this device" #~ msgstr "Dit apparaat verwijderen" #~ msgid "Unpair and remove all settings and files" #~ msgstr "Ontkoppelen en alle instellingen en bestanden verwijderen" #~ msgid "Switch to Bluetooth" #~ msgstr "Overschakelen naar Bluetooth" #~ msgid "Switch to LAN" #~ msgstr "Overschakelen naar LAN" #~ msgid "%s Plugin Failed To Load" #~ msgstr "Kan plug-in %s niet laden" #~ msgid "Reconnect" #~ msgstr "Opnieuw verbinden" #~ msgid "Settings" #~ msgstr "Instellingen" #~ msgid "Additional Software Required" #~ msgstr "Extra software vereist" #~ msgid "Starting Transfer" #~ msgstr "Bezig met starten van overdracht" #~ msgid "" #~ "Transferred files are placed in the Downloads folder." #~ msgstr "" #~ "Overgedragen bestanden worden geplaatst in de map Downloads." #~ msgid "About" #~ msgstr "Over" #~ msgid "KDE Connect" #~ msgstr "KDE Connect" #~ msgid "Debugger" #~ msgstr "Foutopsporing" #~ msgid "Appearance" #~ msgstr "Uiterlijk" #~ msgid "Discoverable" #~ msgstr "Zichtbaar" #~ msgid "Restart Service" #~ msgstr "Dienst herstarten" #~ msgid "Remote Filesystems" #~ msgstr "Externe bestandssystemen" #~ msgid "Sound Effects" #~ msgstr "Geluidseffecten" #~ msgid "Extended Keyboard Support" #~ msgstr "Uitgebreide toetsenbordondersteuning" #~ msgid "Desktop Contacts" #~ msgstr "Bureaubladcontactpersonen" #~ msgid "Additional Features" #~ msgstr "Extra functies" #~ msgid "Click to open preferences" #~ msgstr "Klik om voorkeuren te openen" #~ msgid "Wayland Not Supported" #~ msgstr "Geen Wayland-ondersteuning" #~ msgid "Remote input not supported on Wayland" #~ msgstr "Externe invoer wordt niet ondersteund op Wayland" #~ msgid "GSConnect: %s" #~ msgstr "GSConnect: %s" #~ msgid "Locate Device" #~ msgstr "Apparaat lokaliseren" #~ msgid "%s asked to locate this device" #~ msgstr "%s wil dit apparaat lokaliseren" #~ msgid "Found" #~ msgstr "Gevonden" #~ msgid "Select a contact or number" #~ msgstr "Kies een contactpersoon of telefoonnummer" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/nl_BE.po000066400000000000000000001057351460766671100240140ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-22 10:01+0530\n" "PO-Revision-Date: 2018-11-21 19:57+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: Dutch \n" "Language: nl_BE\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: Poedit 2.1.1\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 #, fuzzy msgid "KDE Connect implementation for GNOME" msgstr "Volledige KDE Connect-implementatie voor GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 #, fuzzy msgid "GSConnect Team" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "" "GSConnect is a complete implementation of KDE Connect especially for GNOME " "Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team " "has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "" "With GSConnect you can securely connect to mobile devices and other desktops " "to:" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 #, fuzzy msgid "Sync contacts" msgstr "Contacten" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 #, fuzzy msgid "Sync notifications" msgstr "Verzendnotificatie" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 #, fuzzy msgid "Control media players" msgstr "Mediaspelers" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 #, fuzzy msgid "Control system volume" msgstr "Systeemvolume" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:127 msgid "GSConnect in GNOME Shell" msgstr "" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Verbind met…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Annuleer" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Verbind" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "" #: data/ui/contact-chooser.ui:56 #, fuzzy msgid "No contacts" msgstr "Contacten" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Hulp" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Typ een telefoonnummer of naam" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Ander" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Verzend SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Verzend" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Toestel is niet geconnecteerd" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 #, fuzzy msgid "Send Message" msgstr "Nieuw bericht" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Typ een bericht" #: data/ui/messaging-conversation.ui:99 #, fuzzy msgid "Message Entry" msgstr "Nieuw bericht" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Berichten" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 #, fuzzy msgid "New Conversation" msgstr "Kies een conversatie" #: data/ui/messaging-window.ui:120 #, fuzzy msgid "No Conversations" msgstr "Kies een conversatie" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "" #: data/ui/messaging-window.ui:196 #, fuzzy msgid "Select or start a conversation" msgstr "Kies een conversatie" #: data/ui/mousepad-input-dialog.ui:97 msgid "" "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n" "\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 #, fuzzy msgid "Edit Command" msgstr "Commando's" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Naam" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Commandoregel" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Kies een uitvoerbaar bestand" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Open" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Desktop" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Klembordsynchronisatie" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Mediaspelers" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Muis en klavier" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Volumebeheer" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Bestanden" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "" #: data/ui/preferences-device-panel.ui:504 #, fuzzy msgid "Save files to" msgstr "Verzend bestanden naar %s" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Deling" #: data/ui/preferences-device-panel.ui:596 #, fuzzy msgid "Device Battery" msgstr "Accu" #: data/ui/preferences-device-panel.ui:647 #, fuzzy msgid "Low Battery Notification" msgstr "Antwoordnotificatie" #: data/ui/preferences-device-panel.ui:706 #, fuzzy msgid "Charged Up to Custom Level Notification" msgstr "Deelnotificaties" #: data/ui/preferences-device-panel.ui:786 #, fuzzy msgid "Fully Charged Notification" msgstr "Deelnotificaties" #: data/ui/preferences-device-panel.ui:840 #, fuzzy msgid "System Battery" msgstr "Accu" #: data/ui/preferences-device-panel.ui:889 #, fuzzy msgid "Share Statistics" msgstr "Deelnotificaties" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Accu" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Commando's" #: data/ui/preferences-device-panel.ui:1032 #, fuzzy msgid "Add Command" msgstr "Commando's" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Deelnotificaties" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Apps" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notificaties" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contacten" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Inkomende oproepen" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Pauzeer media" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "In gesprek" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Microfoon toedoen" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonie" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Actie-sneltoetsen" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Herstel alles…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Sneltoetsen" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Invoegtoepassingen" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "" #: data/ui/preferences-device-panel.ui:2008 #, fuzzy msgid "Device Cache" msgstr "Naar GSM" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Geavanceerd" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Sneltoetsen" #: data/ui/preferences-device-panel.ui:2521 #, fuzzy msgid "Device Settings" msgstr "GSM-instellingen" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Koppel aan" #: data/ui/preferences-device-panel.ui:2597 #, fuzzy msgid "Device is unpaired" msgstr "Toestel is niet geconnecteerd" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Encryptie-info" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Koppel af" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Naar GSM" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Van GSM" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Niets" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Verlagen" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Toedoen" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Stel in" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" "Druk op Esc om te annuleren of op Backspace om de sneltoets terug te zetten." #: data/ui/preferences-window.ui:24 #, fuzzy msgid "Device Name" msgstr "Naar GSM" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Herlaad" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "GSM-instellingen" #: data/ui/preferences-window.ui:166 #, fuzzy msgid "Service Menu" msgstr "Dienst" #: data/ui/preferences-window.ui:189 #, fuzzy msgid "Device Menu" msgstr "Naar GSM" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "" #: data/ui/preferences-window.ui:278 #, fuzzy msgid "Devices" msgstr "Naar GSM" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "" #: data/ui/preferences-window.ui:353 msgid "Behavior When Locked" msgstr "" #: data/ui/preferences-window.ui:378 msgid "Keep Alive" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Browser-add-ons" #: data/ui/preferences-window.ui:728 #, fuzzy msgid "Enable" msgstr "Tablet" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Herkenning uitgeschakeld" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Weergavemodus" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Paneel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Gebruikersmenu" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "" #: data/ui/preferences-window.ui:851 #, fuzzy msgid "About GSConnect" msgstr "GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Kies een GSM" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Kies" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Geen toestel gevonden" #: data/ui/service-device-chooser.ui:118 #, fuzzy msgid "Device List" msgstr "Naar GSM" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Rapporteer" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "" #: data/ui/service-error-dialog.ui:91 msgid "" "GSConnect encountered an unexpected error. Please report the problem and " "include any information that may help." msgstr "" #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Verzend naar GSM" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "Verbind" msgstr[1] "Verbind" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Uitgeschakeld" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Voer een nieuwe sneltoets in voor het wijzigen van %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s is al in gebruik" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Volledige KDE Connect-implementatie voor GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Heimen Stoffels" #: src/preferences/service.js:418 msgid "" "Debug messages are being logged. Take any steps necessary to reproduce a " "problem then review the log." msgstr "" #: src/preferences/service.js:421 msgid "Review Log" msgstr "" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 #, fuzzy msgid "Television" msgstr "Telefonie" #: src/preferences/service.js:517 #, fuzzy msgid "Unpaired" msgstr "Koppel af" #: src/preferences/service.js:521 #, fuzzy msgid "Disconnected" msgstr "Toestel is niet geconnecteerd" #: src/preferences/service.js:525 #, fuzzy msgid "Connected" msgstr "Verbind" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Klik voor probleemoplossing" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Klik voor meer informatie" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Telefoonoproep" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Deel bestand" #: src/service/daemon.js:355 #, fuzzy msgid "List available devices" msgstr "Onbeschikbaar" #: src/service/daemon.js:364 #, fuzzy msgid "List all devices" msgstr "GSM's" #: src/service/daemon.js:373 #, fuzzy msgid "Target Device" msgstr "Naar GSM" #: src/service/daemon.js:415 #, fuzzy msgid "Message Body" msgstr "Nieuw bericht" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Verzendnotificatie" #: src/service/daemon.js:436 #, fuzzy msgid "Notification App Name" msgstr "Notificaties" #: src/service/daemon.js:445 #, fuzzy msgid "Notification Body" msgstr "Notificaties" #: src/service/daemon.js:454 #, fuzzy msgid "Notification Icon" msgstr "Notificaties" #: src/service/daemon.js:463 #, fuzzy msgid "Notification ID" msgstr "Notificaties" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 #, fuzzy msgid "Ring" msgstr "Lokaliseer" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Deel link" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Deel tekst" #: src/service/daemon.js:532 #, fuzzy msgid "Show release version" msgstr "Kies een conversatie" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-toestel op %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, fuzzy, javascript-format msgid "Verification key: %s" msgstr "Notificaties" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Koppelaanvraag van %s" #: src/service/device.js:850 msgid "Reject" msgstr "Verwerp" #: src/service/device.js:855 msgid "Accept" msgstr "Accepteer" #: src/service/manager.js:118 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" "Herkenning is uitgeschakeld vanwege het aantal toestellen op dit netwerk." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:456 #, fuzzy msgid "Port already in use" msgstr "%s is al in gebruik" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, fuzzy, javascript-format msgid "%s: Battery is full" msgstr "%s: batterij is laag" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Volledig opgeladen" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, fuzzy, javascript-format msgid "%d%% Charged" msgstr "Volledig opgeladen" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batterij is laag" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% resterend" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Klembord" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Verstuur klembord" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Haal klembord op" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Vind mijn telefoon" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Touchpad" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 #, fuzzy msgid "Unknown" msgstr "Onbekend contact" #: src/service/plugins/notification.js:20 #, fuzzy msgid "Share notifications with the paired device" msgstr "Geen GSM-notificaties" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Annuleer notificatie" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Sluit notificatie" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Antwoordnotificatie" #: src/service/plugins/notification.js:66 #, fuzzy msgid "Activate Notification" msgstr "Deelnotificaties" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Overzetten niet gelukt" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Kon “%s†niet verzenden naar %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 #, fuzzy msgid "Presentation" msgstr "Bestanden-integratie" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Voer commando's uit" #: src/service/plugins/runcommand.js:17 msgid "" "Run commands on your paired device or let the device run predefined commands " "on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Koppel aan" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Koppel af" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Deel" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 #, fuzzy msgid "Transferring File" msgstr "Overzetten niet gelukt" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "“%s†ontvangen van %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Met succes overgezet" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "“%s†ontvangen van %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Open bestand" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Kon “%s†niet ontvangen van %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Tekst, gedeeld door %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "“%s†verzenden naar %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "“%s†verzonden naar %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Verzend bestanden naar %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 #, fuzzy msgid "Open when done" msgstr "Open in browser" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Verzend een link naar %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nieuwe SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Beantwoord SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Deel SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Systeemvolume" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 #, fuzzy msgid "PulseAudio not found" msgstr "PulseAudio-fout" #: src/service/plugins/telephony.js:18 msgid "" "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Demp oproep" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Onbekend contact" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Inkomende oproep" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "In gesprek" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Job" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "GSM" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Thuis" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Verzend naar %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Zopas" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Gisteren - %s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuut" msgstr[1] "%d minuten" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Onbeschikbaar" #: src/service/ui/messaging.js:757 #, fuzzy msgid "Group Message" msgstr "Nieuw bericht" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (berekenen...)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d%02d tot volledig opgeladen)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d%02d resterend)" #: src/shell/notification.js:58 #, fuzzy msgid "Reply" msgstr "Beantwoord SMS" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Deel links met GSConnect, direct naar de browser of per SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Dienst onbeschikbaar" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Open in browser" #~ msgid "On" #~ msgstr "Aan" #~ msgid "Off" #~ msgstr "Uit" #~ msgid "Set Shortcut" #~ msgstr "Stel sneltoets in" #~ msgid "Authentication Failure" #~ msgstr "Authenticatieprobleem" #~ msgid "Network Error" #~ msgstr "Netwerkfout" #~ msgid "Keyboard not ready" #~ msgstr "Klavier niet gereed" #~ msgid "All files" #~ msgstr "Alle bestanden" #~ msgid "Camera pictures" #~ msgstr "Camera-afbeeldingen" #, fuzzy, javascript-format #~ msgid "%d hour" #~ msgid_plural "%d hours" #~ msgstr[0] "Eén uur" #~ msgstr[1] "%d uur" #, javascript-format #~ msgid "Until %s (%s)" #~ msgstr "Tot %s (%s)" #~ msgid "Do Not Disturb" #~ msgstr "Stoor mij niet" #~ msgid "Until you turn off Do Not Disturb" #~ msgstr "Totdat je Stoor mij niet uitschakelt" #~ msgid "Done" #~ msgstr "Klaar" #~ msgid "Command Shortcuts" #~ msgstr "Commando-sneltoetsen" #~ msgid "Delete" #~ msgstr "Verwijder" #~ msgid "Delete this device" #~ msgstr "Verwijder deze GSM" #~ msgid "Unpair and remove all settings and files" #~ msgstr "Koppel af en wis alle instellingen en bestanden" #~ msgid "Debugger" #~ msgstr "Foutopsporing" #~ msgid "About" #~ msgstr "Over" #~ msgid "Switch to Bluetooth" #~ msgstr "Schakel naar Bluetooth" #~ msgid "Switch to LAN" #~ msgstr "Schakel naar LAN" #~ msgid "Appearance" #~ msgstr "Uiterlijk" #~ msgid "Discoverable" #~ msgstr "Zichtbaar" #~ msgid "Restart Service" #~ msgstr "Herstart dienst" #~ msgid "Settings" #~ msgstr "Instellingen" #~ msgid "Remote Filesystems" #~ msgstr "Externe bestandssystemen" #~ msgid "Sound Effects" #~ msgstr "Geluidseffecten" #~ msgid "Extended Keyboard Support" #~ msgstr "Uitgebreide klavierondersteuning" #~ msgid "Desktop Contacts" #~ msgstr "Bureaubladcontacten" #~ msgid "Additional Features" #~ msgstr "Additionele functionaliteiten" #~ msgid "KDE Connect" #~ msgstr "KDE Connect" #~ msgid "Click to open preferences" #~ msgstr "Klik om voorkeuren te openen" #~ msgid "Additional Software Required" #~ msgstr "Additionele software vereist" #~ msgid "%s Plugin Failed To Load" #~ msgstr "Kon plug-in %s niet laden" #~ msgid "Wayland Not Supported" #~ msgstr "Wayland niet ondersteund" #~ msgid "Remote input not supported on Wayland" #~ msgstr "Externe input wordt niet ondersteund op Wayland" #~ msgid "GSConnect: %s" #~ msgstr "GSConnect: %s" #~ msgid "Reconnect" #~ msgstr "Herverbinden" #~ msgid "Locate Device" #~ msgstr "Lokaliseer GSM" #~ msgid "%s asked to locate this device" #~ msgstr "%s wil deze GSM lokaliseren" #~ msgid "Found" #~ msgstr "Gevonden" #~ msgid "Starting Transfer" #~ msgstr "Overzetten starten" #~ msgid "Select a contact or number" #~ msgstr "Kies een contact of nummer" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/org.gnome.Shell.Extensions.GSConnect.pot000066400000000000000000000713721460766671100321410ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the org.gnome.Shell.Extensions.GSConnect package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-01-02 09:31-0500\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" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:30 msgid "GSConnect" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "" "GSConnect is a complete implementation of KDE Connect especially for GNOME " "Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team " "has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "" "With GSConnect you can securely connect to mobile devices and other desktops " "to:" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:133 msgid "GSConnect in GNOME Shell" msgstr "" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:405 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:293 #: src/service/daemon.js:407 src/service/plugins/sms.js:64 #: webextension/gettext.js:42 msgid "Send SMS" msgstr "" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 msgid "Type a message" msgstr "" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 msgid "New Conversation" msgstr "" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "" #: data/ui/mousepad-input-dialog.ui:97 msgid "" "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n" "\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:482 msgid "Desktop" msgstr "" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:334 msgid "Files" msgstr "" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:16 msgid "Battery" msgstr "" #: data/ui/preferences-device-panel.ui:916 #: data/ui/preferences-device-panel.ui:1001 #: data/ui/preferences-device-panel.ui:2262 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "" #: data/ui/preferences-device-panel.ui:975 msgid "Add Command" msgstr "" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1063 msgid "Share Notifications" msgstr "" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2508 #: data/ui/preferences-device-panel.ui:2600 src/service/daemon.js:386 msgid "Pair" msgstr "" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "" #: data/ui/preferences-device-panel.ui:2555 msgid "You may configure this device before pairing" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2595 src/preferences/device.js:391 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:395 msgid "Unpair" msgstr "" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2657 #: data/ui/preferences-device-panel.ui:2690 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "" #: data/ui/preferences-window.ui:139 src/extension.js:114 msgid "Mobile Settings" msgstr "" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "" #: data/ui/preferences-window.ui:328 src/preferences/service.js:644 msgid "Searching for devices…" msgstr "" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:390 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:402 msgid "Generate Support Log" msgstr "" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:38 msgid "No Device Found" msgstr "" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "" #: data/ui/service-error-dialog.ui:91 msgid "" "GSConnect encountered an unexpected error. Please report the problem and " "include any information that may help." msgstr "" #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "" #. TRANSLATORS: Send to , for file manager #. context menu #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: nautilus-extension/nautilus-gsconnect.py:187 src/service/ui/contacts.js:509 #: src/service/ui/contacts.js:524 #, python-format, javascript-format msgid "Send to %s" msgstr "" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:194 webextension/gettext.js:34 msgid "Send To Mobile Device" msgstr "" #: src/extension.js:50 msgid "Sync between your devices" msgstr "" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "" msgstr[1] "" #: src/preferences/device.js:670 src/preferences/device.js:676 msgid "Edit" msgstr "" #: src/preferences/device.js:685 src/preferences/device.js:691 msgid "Remove" msgstr "" #: src/preferences/device.js:945 src/preferences/device.js:973 msgid "Disabled" msgstr "" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "" #: src/preferences/service.js:403 msgid "" "Debug messages are being logged. Take any steps necessary to reproduce a " "problem then review the log." msgstr "" #: src/preferences/service.js:406 msgid "Review Log" msgstr "" #: src/preferences/service.js:474 msgid "Laptop" msgstr "" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "" #: src/preferences/service.js:478 msgid "Tablet" msgstr "" #: src/preferences/service.js:480 msgid "Television" msgstr "" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "" #: src/preferences/service.js:510 msgid "Connected" msgstr "" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "" #: src/service/daemon.js:194 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:205 msgid "Click for more information" msgstr "" #: src/service/daemon.js:299 msgid "Dial Number" msgstr "" #: src/service/daemon.js:305 src/service/daemon.js:494 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "" #: src/service/daemon.js:356 msgid "List available devices" msgstr "" #: src/service/daemon.js:365 msgid "List all devices" msgstr "" #: src/service/daemon.js:374 msgid "Target Device" msgstr "" #: src/service/daemon.js:416 msgid "Message Body" msgstr "" #: src/service/daemon.js:428 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "" #: src/service/daemon.js:437 msgid "Notification App Name" msgstr "" #: src/service/daemon.js:446 msgid "Notification Body" msgstr "" #: src/service/daemon.js:455 msgid "Notification Icon" msgstr "" #: src/service/daemon.js:464 msgid "Notification ID" msgstr "" #: src/service/daemon.js:473 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "" #: src/service/daemon.js:482 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "" #: src/service/daemon.js:503 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "" #: src/service/daemon.js:512 src/service/plugins/share.js:41 msgid "Share Text" msgstr "" #: src/service/daemon.js:524 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:848 #, javascript-format msgid "Pair Request from %s" msgstr "" #: src/service/device.js:855 msgid "Reject" msgstr "" #: src/service/device.js:860 msgid "Accept" msgstr "" #: src/service/manager.js:118 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/backends/lan.js:169 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:462 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:119 msgid "Fully Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "" #: src/service/plugins/runcommand.js:17 msgid "" "Run commands on your paired device or let the device run predefined commands " "on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "" #: src/service/plugins/sftp.js:193 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:132 src/service/plugins/share.js:208 #: src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "" #: src/service/plugins/telephony.js:18 msgid "" "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "" #: src/service/ui/messaging.js:959 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:124 #, javascript-format msgid "%d%% (Estimating…)" msgstr "" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:133 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "" #: src/shell/notification.js:69 msgid "Reply" msgstr "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:36 msgid "Service Unavailable" msgstr "" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/pl.po000066400000000000000000001106261460766671100234430ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-02 12:14\n" "Last-Translator: \n" "Language-Team: Polish\n" "Language: pl_PL\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==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: pl\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementacja KDE Connect dla Å›rodowiska GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Zespół GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect to peÅ‚na implementacja KDE Connect specjalnie dla PowÅ‚oki GNOME z integracjÄ… z programami Nautilus, Chrome i Firefox. Zespół KDE Connect ma aplikacje dla systemów Linux, BSD, Android, Sailfish, iOS, macOS i Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Za pomocÄ… GSConnect można bezpiecznie połączyć siÄ™ z telefonem i innymi komputerami, aby:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "UdostÄ™pniać pliki, odnoÅ›niki i teksty" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "WysyÅ‚ać i odbierać wiadomoÅ›ci" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Synchronizować zawartość schowka" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Synchronizować kontakty" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Synchronizować powiadomienia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Sterować odtwarzaczami multimedialnymi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Sterować gÅ‚oÅ›noÅ›ciÄ… systemowÄ…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Wykonywać wczeÅ›niej okreÅ›lone polecenia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "I wiele wiÄ™cej…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect w PowÅ‚oce GNOME" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Połącz z…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Anuluj" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Połącz" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Adres IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Brak kontaktów" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Pomoc" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Numer telefonu lub nazwa kontaktu" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Inny" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "WyÅ›lij SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "WyÅ›lij" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "UrzÄ…dzenie jest rozłączone" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "WysyÅ‚a wiadomość" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Napisz wiadomość" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Pole wpisywania wiadomoÅ›ci" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "ProszÄ™ wpisać wiadomość i nacisnąć klawisz Enter, aby jÄ… wysÅ‚ać" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "WiadomoÅ›ci" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nowa rozmowa" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Brak rozmów" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nie wybrano rozmowy" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Wybierz lub rozpocznij rozmowÄ™" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Panel dotykowy.\n" "PrzeciÄ…gniÄ™cie na tym obszarze przeniesie kursor myszy.\n" "DÅ‚ugie naciÅ›niÄ™cie przeciÄ…gnie kursor myszy.\n\n" "Proste klikniÄ™cie zostanie wysÅ‚ane do połączonego urzÄ…dzenia.\n" "Lewy, Å›rodkowy, prawy przycisk i przewiniÄ™cia kółkiem." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Modyfikacja polecenia" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Zapisuje" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nazwa" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Wiersz poleceÅ„" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Wybiera plik wykonywalny" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Otwórz" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Komputer stacjonarny" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Aparat" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Synchronizacja schowka" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Odtwarzacze multimediów" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Mysz i klawiatura" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Sterowanie gÅ‚oÅ›noÅ›ciÄ…" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Pliki" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Odbieranie plików" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Zapisywanie plików w" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "UdostÄ™pnianie" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Akumulator urzÄ…dzenia" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Powiadomienie o niskim poziomie naÅ‚adowania akumulatora" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Powiadomienie o naÅ‚adowaniu do danego poziomu" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Powiadomienie o peÅ‚nym naÅ‚adowaniu" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Akumulator komputera" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "UdostÄ™pnianie statystyk" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Akumulator" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Polecenia" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Dodaj polecenie" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "UdostÄ™pnianie powiadomieÅ„" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "UdostÄ™pnianie podczas aktywnoÅ›ci" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Programy" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Powiadomienia" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontakty" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Połączenia przychodzÄ…ce" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "GÅ‚oÅ›ność" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Wstrzymywanie multimediów" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "TrwajÄ…ce połączenia" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Wyciszanie mikrofonu" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Komunikacja" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Skróty dziaÅ‚aÅ„" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Przywróć wszystko…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Skróty" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Wtyczki" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Eksperymentalne" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Pamięć podrÄ™czna urzÄ…dzenia" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Wyczyść pamięć podrÄ™czną…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "ObsÅ‚uga SMS (poprzednia wersja)" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Automatyczne montowanie SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Zaawansowane" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Skróty klawiszowe" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Ustawienia urzÄ…dzenia" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Powiąż" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "UrzÄ…dzenie jest niepowiÄ…zane" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Można skonfigurować to urzÄ…dzenie przed powiÄ…zaniem" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informacje o szyfrowaniu" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Odwiąż" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Do urzÄ…dzenia" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Z urzÄ…dzenia" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Nic" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Przywróć" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Ciszej" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Wycisz" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Ustaw" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Klawisz Esc anuluje, a Backspace przywróci skrót klawiszowy." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nazwa urzÄ…dzenia" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_ZmieÅ„ nazwÄ™" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "OdÅ›wież" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Ustawienia urzÄ…dzeÅ„ mobilnych" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menu usÅ‚ugi" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menu urzÄ…dzenia" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Modyfikuje nazwÄ™ urzÄ…dzenia" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "UrzÄ…dzenia" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Wyszukiwanie urzÄ…dzeń…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Ustawienia rozszerzenia" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "Bez wyłączania GSConnect po zablokowaniu PowÅ‚oki GNOME" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Dodatki do przeglÄ…darek" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Włącz" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "To urzÄ…dzenie jest niewidoczne dla niepowiÄ…zanych urzÄ…dzeÅ„" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Wykrywanie jest wyłączone" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Tryb wyÅ›wietlania" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Menu użytkownika" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Utwórz dziennik wsparcia" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "O programie" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Wybór urzÄ…dzenia" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Wybierz" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Nie odnaleziono żadnego urzÄ…dzenia" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Lista urzÄ…dzeÅ„" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "ZgÅ‚oÅ›" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "CoÅ› siÄ™ nie powiodÅ‚o" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "WystÄ…piÅ‚ nieoczekiwany błąd w GSConnect. ProszÄ™ go zgÅ‚osić i dołączyć wszelkie informacje, które mogÄ… pomóc." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Informacje techniczne" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "WyÅ›lij na urzÄ…dzenie mobilne" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Synchronizacja pomiÄ™dzy urzÄ…dzeniami" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d połączone urzÄ…dzenie" msgstr[1] "%d połączone urzÄ…dzenia" msgstr[2] "%d połączonych urzÄ…dzeÅ„" msgstr[3] "%d połączonych urzÄ…dzeÅ„" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Modyfikuje" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Usuwa" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Wyłączone" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "ProszÄ™ wprowadzić nowy skrót, aby zmienić „%sâ€" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s jest już używane" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "PeÅ‚na implementacja KDE Connect dla Å›rodowiska GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Adrian KryÅ„ski , 2017\n" "Piotr DrÄ…g , 2018-2020\n" "Aviary.pl , 2018-2020" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Komunikaty debugowania sÄ… zapisywane w dzienniku. ProszÄ™ podjąć dziaÅ‚ania niezbÄ™dne do powtórzenia problemu, a nastÄ™pnie przejrzeć dziennik." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Przejrzyj dziennik" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartfon" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Telewizor" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "NiepowiÄ…zane" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Rozłączone" #: src/preferences/service.js:525 msgid "Connected" msgstr "Połączone" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Oczekiwanie na usÅ‚ugę…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Uzyskaj pomoc w rozwiÄ…zaniu problemu" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "WiÄ™cej informacji" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "ZadzwoÅ„" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "UdostÄ™pnij plik" #: src/service/daemon.js:355 msgid "List available devices" msgstr "WyÅ›wietla listÄ™ dostÄ™pnych urzÄ…dzeÅ„" #: src/service/daemon.js:364 msgid "List all devices" msgstr "WyÅ›wietla listÄ™ wszystkich urzÄ…dzeÅ„" #: src/service/daemon.js:373 msgid "Target Device" msgstr "UrzÄ…dzenie docelowe" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Treść wiadomoÅ›ci" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "WyÅ›lij powiadomienie" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Nazwa aplikacji powiadomienia" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Treść powiadomienia" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Ikona powiadomienia" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Identyfikator powiadomienia" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "ZdjÄ™cie" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "ZadzwoÅ„" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "UdostÄ™pnij odnoÅ›nik" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "UdostÄ™pnij tekst" #: src/service/daemon.js:532 msgid "Show release version" msgstr "WyÅ›wietla wersjÄ™ wydania" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "UrzÄ…dzenie Bluetooth pod adresem %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Klucz weryfikacji: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "ProÅ›ba o powiÄ…zanie z urzÄ…dzenia %s" #: src/service/device.js:850 msgid "Reject" msgstr "Odrzuć" #: src/service/device.js:855 msgid "Accept" msgstr "Przyjmij" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Wykrywanie zostaÅ‚o wyłączone z powodu liczby urzÄ…dzeÅ„ w tej sieci." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "Nie odnaleziono biblioteki OpenSSL" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Port jest już używany" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Wymiana informacji o naÅ‚adowaniu akumulatora" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: akumulator jest w peÅ‚ni naÅ‚adowany" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "W peÅ‚ni naÅ‚adowane" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: akumulator zostaÅ‚ naÅ‚adowany do danego poziomu" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "NaÅ‚adowano %d%%" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: poziom naÅ‚adowania akumulatora jest niski" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "PozostaÅ‚o %d%%" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Schowek" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "UdostÄ™pnianie zawartoÅ›ci schowka" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "WysyÅ‚anie do schowka" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Odbieranie ze schowka" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "DostÄ™p do kontaktów powiÄ…zanego urzÄ…dzenia" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Znajdź mój telefon" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Dzwonienie na powiÄ…zane urzÄ…dzenie" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "PodkÅ‚adka pod mysz" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Umożliwienie używania powiÄ…zanego urzÄ…dzenia jako zdalnej myszy i klawiatury" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Zdalne sterowanie" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Dwukierunkowe zdalne sterowanie odtwarzaniem multimediów" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Nieznany" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "UdostÄ™pnianie powiadomieÅ„ powiÄ…zanemu urzÄ…dzeniu" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Anuluj powiadomienie" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Zamknij powiadomienie" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Odpowiedz na powiadomienie" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Aktywuj powiadomienie" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "PowiÄ…zane urzÄ…dzenie robi zdjÄ™cie i przesyÅ‚a je do komputera" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "PrzesÅ‚anie siÄ™ nie powiodÅ‚o" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "WysÅ‚anie „%s†do urzÄ…dzenia %s siÄ™ nie powiodÅ‚o" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "WysyÅ‚anie i odbieranie sygnałów ping" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Prezentacja" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Używanie powiÄ…zanego urzÄ…dzenia jako prezentera" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Wykonywanie poleceÅ„" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Wykonywanie poleceÅ„ na powiÄ…zanym urzÄ…dzeniu lub umożliwienie urzÄ…dzeniu wykonywania wczeÅ›niej ustalonych poleceÅ„ na tym komputerze" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "PrzeglÄ…danie systemu plików powiÄ…zanego urzÄ…dzenia" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Zamontuj" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Odmontuj" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "UrzÄ…dzenie %s zgÅ‚osiÅ‚o błąd" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "UdostÄ™pnij" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "UdostÄ™pnianie plików i adresów URL miÄ™dzy urzÄ…dzeniami" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "UrzÄ…dzenie %s nie może wysyÅ‚ać plików" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "PrzesyÅ‚anie pliku" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Odbieranie „%s†z urzÄ…dzenia %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "PomyÅ›lnie przesÅ‚ano" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Odebrano „%s†z urzÄ…dzenia %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "WyÅ›wietl poÅ‚ożenie pliku" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Otwórz plik" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Odebranie „%s†z urzÄ…dzenia %s siÄ™ nie powiodÅ‚o" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Tekst udostÄ™pniony przez: %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "WysyÅ‚anie „%s†do urzÄ…dzenia %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "WysÅ‚ano „%s†do urzÄ…dzenia %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "WysÅ‚anie plików do urzÄ…dzenia %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Otwarcie po ukoÅ„czeniu" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "WysyÅ‚a odnoÅ›nik do urzÄ…dzenia %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "WysyÅ‚anie i odczytywanie wiadomoÅ›ci SMS powiÄ…zanego urzÄ…dzenia i powiadamianie o nowych SMS-ach" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nowy SMS (adres URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Odpowiedz na SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "UdostÄ™pnij SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "GÅ‚oÅ›ność systemu" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Umożliwienie powiÄ…zanemu urzÄ…dzeniu sterowania gÅ‚oÅ›noÅ›ciÄ… systemu" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "Nie odnaleziono usÅ‚ugi PulseAudio" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Powiadamianie o połączeniach i dostosowywanie gÅ‚oÅ›noÅ›ci systemu podczas oczekujÄ…cych/trwajÄ…cych połączeÅ„" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Wycisz połączenie" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Nieznany kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Połączenie przychodzÄ…ce" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "TrwajÄ…ce połączenie" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Faks" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "SÅ‚użbowy" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Komórkowy" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Domowy" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "WyÅ›lij do „%sâ€" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Przed chwilÄ…" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Wczoraj・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuta" msgstr[1] "%d minuty" msgstr[2] "%d minut" msgstr[3] "%d minut" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "NiedostÄ™pne" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Wiadomość grupowa" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Ja: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "I %d inny kontakt" msgstr[1] "I %d inne kontakty" msgstr[2] "I %d innych kontaktów" msgstr[3] "I %d innych kontaktów" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Zdalna klawiatura na urzÄ…dzeniu %s nie jest aktywna" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (obliczanie…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (do naÅ‚adowania: %d∶%02d)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (pozostaÅ‚o: %d∶%02d)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Odpowiedz" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "UdostÄ™pnianie odnoÅ›ników za pomocÄ… GSConnect, bezpoÅ›rednio do przeglÄ…darki lub przez wiadomość SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "UsÅ‚uga jest niedostÄ™pna" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Otwórz w przeglÄ…darce" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/pt.po000066400000000000000000001071041460766671100234500ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-02 19:26\n" "Last-Translator: \n" "Language-Team: Portuguese\n" "Language: pt_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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: pt-PT\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementação do KDE Connect para o GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Equipa do GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "O GSConnect é uma implementação completa do KDE Connect especialmente para a GNOME Shell com integração Nautilus, Chrome e Firefox. A equipa do KDE Connect tem aplicações para Linux, BSD, Android, Sailfish, iOS, macOS e Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Com o GSConnect pode ligar-se de forma segura a dispositivos móveis e outros ambientes de trabalho para:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Partilhar ficheiros, ligações e texto" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Enviar e receber mensagens" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Sinconizar conteúdo da área de transferência" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Sincronizar contactos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Sincronizar notificações" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Controlar leitores multimédia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Controlar volume do sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Executar comandos predefinidos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "E mais…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect na GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Ligar a…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Cancelar" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Ligar" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Endereço de IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Nenhum contacto" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Ajuda" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Introduza um número de telefone ou nome" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Outros" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Enviar SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Enviar" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "O dispositivo está desligado" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Enviar mensagem" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Escreva uma mensagem" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Entrada de mensagens" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Escreva uma mensagem e prima Enter para enviar" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Mensagens" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nova conversa" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Sem conversas" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nenhuma conversa selecionada" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Selecionar ou iniciar uma conversa" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Touchpad.\n" "Arraste esta área para mover o cursor do rato.\n" "Pressione para arrastar e arrastar o cursor do rato\n\n" "Clique simples será enviado para o dispositivo emparelhado.\n" "Botão esquerdo, meio, direito e deslocamentos com a roda." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Editar comando" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Guardar" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nome" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Linha de comandos" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Escolha um executável" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Abrir" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Ãrea de trabalho" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Câmara" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Sincronizar a área de transferência" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Leitores multimédia" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Rato e Teclado" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Controlo de volume" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Ficheiros" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Receber ficheiros" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Guardar ficheiros para" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Partilhar" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Bateria do dispositivo" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Notificação de bateria fraca" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Carregado até à notificação de nível personalizada" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Notificação de carga completa" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Bateria do sistema" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Partilhar estatísticas" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Bateria" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Comandos" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Adicionar comando" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Partilhar notificações" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Partilhar quando ativo" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Aplicações" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notificações" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contactos" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Chamadas recebidas" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Colocar em pausa" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Chamadas em curso" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Silenciar microfone" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonia" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Atalhos de ação" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Repor tudo…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Atalhos" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Extensões" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimental" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Cache do dispositivo" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Limpar cache…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Suporte por SMS antigo" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Montagem automática SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avançado" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Teclas de atalho" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Definições do dispositivo" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Emparelhar" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "O dispositivo não está emparelhado" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Pode configurar este dispositivo antes de emparelhar" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informação de encriptação" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Desemparelhar" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Para o dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Do dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Nada" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Baixo" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Silenciar" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Definir" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Prima Esc para cancelar ou Backspace para repor a tecla de atalho." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nome do dispositivo" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Mudar o nome" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Recarregar" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Definições móveis" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menu de serviço" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menu de dispositivos" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Editar nome do dispositivo" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Dispositivos" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "A procurar por dispositivos…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Definições das extensões" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "O GSConnect permanece ativo quando a GNOME Shell está bloqueada" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Add-Ons do navegador" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Ativar" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Este dispositivo é invisível a dispositivos não emparelhados" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Detecção desativada" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Modo de visualização" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Painel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Menu do utilizador" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Gerar registo de suporte" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Acerca do GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Selecione um dispositivo" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Selecionar" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Nenhum dispositivo encontrado" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Lista de dispositivos" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Reportar" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Alguma coisa correu mal" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "O GSConnect encontrou um erro inesperado. Reporte o problema e inclua todas as informações que possam ajudar." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Informação técnica" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Enviar para dispositivo móvel" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Sincronize entre os seus dispositivos" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d ligado" msgstr[1] "%d ligado" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Editar" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Remover" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Desativado" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Introduza um novo atalho para alterar %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "O %s já está a ser usado" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Uma implementação completa do KDE Connect para o GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Duarte Loreto \n" "Tiago Santos \n" "Pedro Albuquerque \n" "Juliano de Souza Camargo \n" "Hugo Carvalho " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "As mensagens de depuração estão a ser registadas. Tomar todas as medidas necessárias para reproduzir um problema e depois rever o registo." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Registo de revisão" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Portátil" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisão" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Desemparelhado" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Desligado" #: src/preferences/service.js:525 msgid "Connected" msgstr "Ligado" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "À espera de serviço…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Clique para ajudar a solucionar problemas" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Clique para mais informação" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Marcar número" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Partilhar ficheiro" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Listar dispositivos disponíveis" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Listar todos os dispositivos" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Para o dispositivo" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Corpo da mensagem" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Enviar notificação" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Nome da aplicação de notificação" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Corpo da notificação" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Ãcone de notificação" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID da notificação" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Toque" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Partilhar ligação" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Partilhar texto" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Mostrar versão de lançamento" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositivo Bluetooth em %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Chave de verificação: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Pedido para emparelhar de %s" #: src/service/device.js:850 msgid "Reject" msgstr "Rejeitar" #: src/service/device.js:855 msgid "Accept" msgstr "Aceitar" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "A deteção foi desativada devido ao número de dispositivos nesta rede." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL não encontrado" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Porta já em utilização" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Trocar informações de bateria" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Bateria está carregada" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Totalmente carregado" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Bateria atingiu o nível de carga personalizada" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Carregada" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: A bateria está fraca" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% restante" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Ãrea de transferência" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Partilhar o conteúdo da área de transferência" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Enviar da área de transferência" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Carregar da área de transferência" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Aceder a contactos do dispositivo emparelhado" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Encontrar o meu telefone" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Tocar no seu dispositivo emparelhado" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Tapete de rato" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Permite que o dispositivo emparelhado funcione como um rato e teclado remotos" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Entrada remota" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Controlo remoto bidirecional de reprodução multimédia" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Desconhecida" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Partilhar notificações com o dispositivo emparelhado" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Cancelar notificação" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Fechar notificação" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Enviar notificação" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Ativar notificações" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Solicitar ao dispositivo emparelhado para tirar uma fotografia e transferi-la para este PC" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Falha ao transferir" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Falha ao enviar “%s†para %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Envie e receba pings" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Apresentação" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Usar o dispositivo emparelhado como apresentador" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Executar comandos" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Executar comandos no dispositivo emparelhado ou permitir que o dispositivo execute comandos predefinidos neste PC" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Navegar pelo sistema de ficheiros do dispositivo emparelhado" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s relatou um erro" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Partilha" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Partilhar ficheiros e URLs entre dispositivos" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s não tem permissão para carregar ficheiros" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "A transferir ficheiro" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "A receber \"%s\" de %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Transferência bem sucedida" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Recebeu \"%s\" de %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Mostrar localização do ficheiro" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Abrir ficheiro" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Falha ao receber “%s†de %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Texto partilhado por %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "A enviar \"%s\" para %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Enviou \"%s\" para %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Enviar ficheiros para %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Abrir quando terminar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Enviar um ligação para %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Enviar e ler SMS do dispositivo emparelhado e ser notificado sobre o novo SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Novo SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Responder SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Partilhar SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volume do sistema" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Ativar o dispositivo emparelhado para controlar o volume do sistema" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio não encontrado" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Ser notificado sobre as chamadas e ajustar o volume do sistema durante o toque/chamadas em curso" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silenciar chamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Contacto desconhecido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Chamada recebida" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Chamada em curso" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Trabalho" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Telemóvel" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Casa" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Enviar para %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Agora mesmo" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Ontem・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Não disponível" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Mensagem para grupo" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Você: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "E %d outro contacto" msgstr[1] "E %d outros" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Teclado remoto na %s não está ativo" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (a estimar…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d até ficar carregada)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d restante)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Partilhar ligações com o GSConnect, diretamente para o navegador ou por SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Serviço indisponível" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Abrir no navegador" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/pt_BR.po000066400000000000000000001052431460766671100240350ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Portuguese, Brazilian\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: pt-BR\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementação do KDE Connect para o GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Equipe GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Com o GSConnect, você pode se conectar de forma segura a dispositivos móveis e outros desktops para:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Compartilhar arquivos, links e texto" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Enviar e receber mensagens" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Sincronizar o conteúdo da área de transferência" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Sincronizar contatos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Sincronizar notificações" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Controlar reprodutores de mídia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Controlar o volume do sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Executar comandos predefinidos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "E mais…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect no GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Conectar a…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Cancelar" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Conectar" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Endereço IP" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Nenhum contato" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Ajuda" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Digite um número de telefone ou nome" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Outros" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Enviar SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Enviar" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Dispositivo está desconectado" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Enviar mensagem" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Escrever uma mensagem" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Entrada de mensagem" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Digite uma mensagem e pressione Enter para enviar" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Mensagens" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nova conversa" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Nenhuma conversa" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nenhuma conversa selecionada" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Selecionar ou iniciar uma conversa" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Editar comando" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Salvar" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Nome" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Linha de comando" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Escolher um executável" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Abrir" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Computador" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Câmera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Sincronização da área de transferência" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Reprodutores de mídia" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Mouse & teclado" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Controle de volume" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Arquivos" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Receber arquivos" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Salvar arquivos em" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Compartilhamento" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Bateria do dispositivo" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Notificação de bateria fraca" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Notificação de nível de carga personalizado" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Notificação de bateria carregada" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Bateria do sistema" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Compartilhar estatísticas" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Bateria" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Comandos" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Adicionar comando" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Compartilhar notificações" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Compartilhar quanto ativo" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Aplicativos" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Notificações" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Contatos" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Chamadas recebidas" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Pausar mídia" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Chamadas em andamento" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Silenciar microfone" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonia" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Atalhos de ações" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Redefinir tudo…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Atalhos" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Plugins" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimental" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Cache do dispositivo" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Limpar cache…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Suporte a SMS legado" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Automontagem SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avançado" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Atalhos de teclado" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Configurações do dispositivo" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Parear" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "O dispositivo não está pareado" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Você pode configurar este dispositivo antes de parear" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informação de criptografia" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Esquecer" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Para o dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Do dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Nada" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Baixo" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Silenciar" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Definir" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Pressione Esc para cancelar ou Backspace para redefinir o atalho de teclado." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Nome do dispositivo" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Renomear" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Recarregar" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Configurações de dispositivos" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Menu do serviço" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Menu do dispositivo" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Edita o nome do dispositivo" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Dispositivos" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Procurando dispositivos…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Complementos para navegadores" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Habilitar" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Este dispositivo está invisível para dispositivos não pareados" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Descoberta desativada" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Modo de exibição" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Painel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Menu do usuário" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Gerar log de suporte" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Sobre o GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Selecione um dispositivo" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Selecionar" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Nenhum dispositivo encontrado" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Lista de dispositivos" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Reportar" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Algo está errado" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect encontrou um erro inesperado. Por favor, relate o problema e inclua todas as informações que possam ajudar." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Detalhes técnicos" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Enviar para dispositivo móvel" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d conectado" msgstr[1] "%d conectados" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Editar" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Remover" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Desativado" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Digite um novo atalho para mudar %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s já está em uso" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Uma implementação completa do KDE Connect para o GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Ricardo Silva Veloso \n" "Rafael Fontenelle " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Mensagens de depuração estão sendo registradas. Utilize quaisquer etapas necessárias para reproduzir um problema e consulte o log." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Consultar log" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Notebook" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Televisão" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Não pareado" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Desconectado" #: src/preferences/service.js:525 msgid "Connected" msgstr "Conectado" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Aguardando serviço…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Clique para ajuda na solução de problemas" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Clique para mais informação" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Ligar para número" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Compartilhar arquivo" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Listar dispositivos disponíveis" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Listar todos dispositivos" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Dispositivo de destino" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Corpo da mensagem" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Enviar notificação" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Nome do aplicativo de notificação" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Corpo da notificação" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Ãcone da notificação" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID da notificação" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Tocar" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Compartilhar link" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Compartilhar texto" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Mostrar versão de lançamento" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositivo Bluetooth em %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Convite de pareamento de %s" #: src/service/device.js:850 msgid "Reject" msgstr "Rejeitar" #: src/service/device.js:855 msgid "Accept" msgstr "Aceitar" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "A descoberta foi desativada devido ao número de dispositivos nesta rede." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL não encontrado" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "A porta já está em uso" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Trocar informações sobre bateria" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Bateria cheia" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Carregado" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Bateria atingiu o nível de carga personalizada" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Carregada" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: bateria fraca" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% restante" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Ãrea de transferência" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Compartilhar conteúdo da área de transferência" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Enviar para área de transferência" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Pegar da área de transferência" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Acessar contatos do aparelho pareado" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Encontrar meu smartphone" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Tocar no seu dispositivo pareado" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Permite que o dispositivo pareado funcione como mouse e teclado remotos" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Controle remoto de reprodução multimídia bidirecional" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Desconhecido" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Compartilhar notificações com o dispositivo pareado" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Cancelar notificação" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Fechar notificação" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Responder notificação" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Ativar notificação" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Pedir que dispositivo pareado tire foto e transfira para este PC" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Transferência falhou" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Falha ao enviar “%s†para %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Envie e receba pings" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Apresentação" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Usar dispositivo conectado como apresentador" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Executar comandos" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Executar comandos em seu dispositivo conectado ou permitir que o dispositivo execute comandos predefinidos neste PC" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Navegar pelos arquivos do dispositivo conectado" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s relatou um erro" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Compartilhar" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Compartilhar arquivos e URLs entre dispositivos" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s não tem permissão para enviar arquivos" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Transferindo arquivo" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Recebendo “%s†de %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Transferência completa" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Recebido “%s†de %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Abrir arquivo" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Falha ao receber “%s†de %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Texto compartilhado por %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Enviando “%s†para %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Enviado “%s†para %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Enviar arquivos para %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Abrir quando terminar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Enviar um link para %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Enviar e ler SMS do dispositivo conectado e ser notificado sobre o novo SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Novo SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Responder SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Compartilhar SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Volume do sistema" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Ative o dispositivo emparelhado para controlar o volume do sistema" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio não encontrado" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Ser notificado sobre chamadas e ajustar o volume do sistema durante chamadas" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silenciar chamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Contato desconhecido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Recebendo chamada" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Chamada em andamento" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Trabalho" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Celular" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Residencial" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Enviar para %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Agora" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Ontem・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Indisponível" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Mensagem de grupo" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Você: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "E %d outro contato" msgstr[1] "E %d outros" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "O teclado remoto em %s não está ativo" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Estimando…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d até a carga completa)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d restante)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Compartilhe links com o GSConnect, diretamente no navegador ou por SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Serviço indisponível" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Abrir no navegador" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/ru.po000066400000000000000000001173671460766671100234670ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-08-25 22:45\n" "Last-Translator: \n" "Language-Team: Russian\n" "Language: ru_RU\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%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: ru\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Ð ÐµÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ KDE Connect Ð´Ð»Ñ GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Команда GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect - Ñто Ð¿Ð¾Ð»Ð½Ð°Ñ Ñ€ÐµÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ KDE Connect Ð´Ð»Ñ GNOME Shell Ñ Ð¸Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸ÐµÐ¹ в Nautilus, Chrome и Firefox. Команда KDE Connect также имеет Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Linux, BSD, Android, Sailfish, iOS, macOS и Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "С помощью GSConnect вы можете безопаÑно подключитьÑÑ Ðº мобильным уÑтройÑтвам и другим компьютерам:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "ПоделитьÑÑ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸, ÑÑылками и текÑтом" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Отправить и получить ÑообщениÑ" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Синхронизировать буфер обмена" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Синхронизировать контакты" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Синхронизировать уведомлениÑ" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "УправлÑть проигрывателÑми" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "УправлÑть ÑиÑтемной громкоÑтью" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "ВыполнÑть заданные команды" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "И другое…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect в GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "ПодключитьÑÑ Ðºâ€¦" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Отмена" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "ПодключитьÑÑ" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP адреÑ" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Ðет контактов" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Помощь" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Ðаберите номер или имÑ" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Другие" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Отправить СМС" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Отправить" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "УÑтройÑтво отключено" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Отправить Ñообщение" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Ðабрать Ñообщение" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Сообщение" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Введите Ñообщение и нажмите Enter Ð´Ð»Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Сообщение" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Ðовый диалог" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Ðет диалогов" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Ðе выбран диалог" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Выберите или начните диалог" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Тачпад\n" "Ð”Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÑƒÑ€Ñором удерживайте ЛКМ и перемещайте мышь.\n\n" "Одинарный клик будет отправлен на ÑопрÑжённое уÑтройÑтво.\n" "ИÑпользуйте левую, Ñреднюю и правую кнопку мыши, а также колеÑо прокрутки." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Изменить команду" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Сохранить" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "ИмÑ" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "ÐšÐ¾Ð¼Ð°Ð½Ð´Ð½Ð°Ñ Ñтрока" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Выберите иÑполнÑемый файл" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Открыть" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Компьютер" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Камера" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð±ÑƒÑ„ÐµÑ€Ð° обмена" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Проигрыватели" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Мышь и клавиатура" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Управление громкоÑтью" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Файлы" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "ПринÑть файлы" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Сохранить файлы в" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Общий доÑтуп и обмен" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Ð‘Ð°Ñ‚Ð°Ñ€ÐµÑ ÑƒÑтройÑтва" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Уведомление о низком уровне зарÑда" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Уведомление о зарÑдке до пользовательÑкого уровнÑ" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Уведомление о полной зарÑдке" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð±Ð°Ñ‚Ð°Ñ€ÐµÑ" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "ПоделитьÑÑ ÑтатиÑтикой" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "БатареÑ" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Команды" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Добавить команду" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "ОтправлÑть уведомлениÑ" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "ПоделитьÑÑ Ð¿Ñ€Ð¸ доÑтупноÑти" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "ПриложениÑ" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "УведомлениÑ" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Контакты" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "При входÑщем вызове" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "ГромкоÑть" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "ПриоÑтановить плеер" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "При иÑходÑщем вызове" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Выключить микрофон" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "ТелефониÑ" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Комбинации клавиш" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "СброÑить вÑе…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Комбинации клавиш" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Плагины" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "ЭкÑпериментальное" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Кеш уÑтройÑтва" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "ОчиÑтить кеш…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "УÑÑ‚Ð°Ñ€ÐµÐ²ÑˆÐ°Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ° SMS" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Ðвтомонтирование SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Дополнительные" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Комбинации клавиш" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "ÐаÑтройки уÑтройÑтва" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "СопрÑжение" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "УÑтройÑтво не ÑопрÑжено" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Ð’Ñ‹ можете наÑтроить Ñто уÑтройÑтво перед ÑопрÑжением" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ шифровании" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Забыть" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Ðа уÑтройÑтво" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "С уÑтройÑтва" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Ðичего" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Вернуть" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Тише" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Выключить" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Выбор" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Ðажмите Esc Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹ или Backspace чтобы ÑброÑить комбинацию." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Ðазвание уÑтройÑтва" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Переименовать" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Обновить" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "ÐаÑтройки" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "СервиÑное меню" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Меню уÑтройÑтва" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Редактировать название уÑтройÑтва" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "УÑтройÑтва" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "ПоиÑк уÑтройÑтв…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "ÐаÑтройки раÑширениÑ" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect оÑтаетÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ‹Ð¼, когда рабочий Ñтол заблокирован" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "РаÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð°" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Включить" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Это уÑтройÑтво ÑвлÑетÑÑ Ð½ÐµÐ²Ð¸Ð´Ð¸Ð¼Ñ‹Ð¼ Ð´Ð»Ñ Ð½ÐµÑпаренных уÑтройÑтв" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Обнаружение выключено" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Режим отображениÑ" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Панель" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Меню пользователÑ" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Сгенерировать журнал" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "О GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Выберите уÑтройÑтво" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Выбор" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "УÑтройÑтва не найдены" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "СпиÑок уÑтройÑтв" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Отзыв" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Что-то пошло не так" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "Возникла Ð½ÐµÐ¿Ñ€ÐµÐ´Ð²Ð¸Ð´ÐµÐ½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°. ПожалуйÑта, Ñообщите о проблеме, по возможноÑти предоÑтавьте дополнительную информацию." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "ТехничеÑÐºÐ°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Отправить на уÑтройÑтво" #: src/extension.js:52 msgid "Sync between your devices" msgstr "СинхронизируйтеÑÑŒ между Ñвоими уÑтройÑтвами" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d подключен" msgstr[1] "%d подключено" msgstr[2] "%d подключено" msgstr[3] "%d подключено" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Изменить" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Удалить" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Отключено" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Введите новую комбинацию клавиш Ð´Ð»Ñ %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s уже иÑпользуетÑÑ" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "ÐŸÐ¾Ð»Ð½Ð°Ñ Ñ€ÐµÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ KDE Connect Ð´Ð»Ñ GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "'Losted' " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Отладочные ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ запиÑаны. Произведите дейÑÑ‚Ð²Ð¸Ñ Ð¿Ñ€Ð¸ которых произошла проблема, затем поÑмотрите журнал." #: src/preferences/service.js:421 msgid "Review Log" msgstr "ПоÑмотреть журнал" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Ðоутбук" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Смартфон" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Планшет" #: src/preferences/service.js:495 msgid "Television" msgstr "Телевидение" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Ðе ÑопрÑжен" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Отключено" #: src/preferences/service.js:525 msgid "Connected" msgstr "Подключено" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Ожидание Ñлужбы…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Ðажмите Ð´Ð»Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Ðажмите Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Вызвать номер" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "ПоделитьÑÑ Ñ„Ð°Ð¹Ð»Ð¾Ð¼" #: src/service/daemon.js:355 msgid "List available devices" msgstr "СпиÑок доÑтупных уÑтройÑтв" #: src/service/daemon.js:364 msgid "List all devices" msgstr "СпиÑок вÑех уÑтройÑтв" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Целевое уÑтройÑтво" #: src/service/daemon.js:415 msgid "Message Body" msgstr "ТекÑÑ‚ ÑообщениÑ" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Отправить" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Ð˜Ð¼Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "ТекÑÑ‚ уведомлениÑ" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Иконка уведомлениÑ" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID уведомлениÑ" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Фото" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Пинг" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Ðайти" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "ПоделитьÑÑ ÑÑылкой" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "ПоделитьÑÑ Ñ‚ÐµÐºÑтом" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Показать верÑию программы" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth уÑтройÑтво на %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Ключ проверки: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ ÑопрÑÐ¶ÐµÐ½Ð¸Ñ Ð¾Ñ‚ %s" #: src/service/device.js:850 msgid "Reject" msgstr "Отклонить" #: src/service/device.js:855 msgid "Accept" msgstr "ПринÑть" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Обнаружение было выключено из-за количеÑтва уÑтройÑтв в Ñтой Ñети." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL не найден" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Порт уже иÑпользуетÑÑ" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Оповещать о ÑоÑтоÑнии батареи" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: ÐккумулÑтор зарÑжен" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "ПолноÑтью зарÑжено" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Уровень зарÑда батареи доÑтиг пользовательÑкой отметки" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% зарÑжено" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: ÐккумулÑтор разрÑжен" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% оÑталоÑÑŒ" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Буфер обмена" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "ПоделитьÑÑ Ñодержимым буфера обмена" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Отправить Буфер обмена" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "ЗапроÑить Буфер обмена" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "Получить доÑтуп к контактам ÑопрÑжённого уÑтройÑтва" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Ðайти мой Ñмартфон" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Сделать гудок вашим уÑтройÑтвом" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Тачпад" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "ПозволÑет ÑопрÑжённому уÑтройÑтву удалённо работать мышью и клавиатурой" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Удалённый Ввод" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Двунаправлённое управление воÑпроизведением" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "ÐеизвеÑтно" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "ПоделитьÑÑ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñми Ñ ÑопрÑжённым уÑтройÑтвом" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Отменить уведомление" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Закрыть уведомление" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Ответить" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Ðктивировать уведомление" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "ЗапроÑить уÑтройÑтво Ñделать Ñнимок и отправить его на Ñтот ПК" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Передача не удалаÑÑŒ" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Ðе удалоÑÑŒ отправить «%s» на %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Отправка и получение пингов" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Пинг: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "ПрезентациÑ" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "ИÑпользовать ÑопрÑжённое уÑтройÑтво Ð´Ð»Ñ Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ð¸" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Отправить Команду" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Выполнить команды на ÑопрÑжённом уÑтройÑтве или позволить ему запуÑтить определённые команды на Ñтом ПК" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "ПроÑмотреть файловую ÑиÑтему ÑопрÑжённого уÑтройÑтва" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Примонтировать" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Размонтировать" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s Ñообщил об ошибке" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "ПоделитьÑÑ" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "ДелитьÑÑ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸ и ÑÑылками между уÑтройÑтвами" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s не разрешено загружать файлы" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Передача файла" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Получение «%s» от %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Передача завершена" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Получен «%s» от %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Показать РаÑположение Файла" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Открыть файл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Ðе удалоÑÑŒ получить«%s» от %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "ТекÑÑ‚ получен Ñ %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Отправка «%s» на %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Отправлено «%s» на %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Отправить файлы на %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Открыть при завершении" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Отправить ÑÑылку на %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Отправка и чтение SMS Ñ ÑƒÑтройÑтва и получение уведомлений о новых SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Ðовое Ñообщение (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Ответить на SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Отправить SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "ГромкоÑть" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Разрешить ÑопрÑжённому уÑтройÑтву управлÑть громкоÑтью ÑиÑтемы" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio не найден" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "УведомлÑть о звонках и регулировать громкоÑть ÑиÑтемы во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð²Ð¾Ð½ÐºÐ¾Ð²" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Отключить звонок" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "ÐеизвеÑтный контакт" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "ВходÑщий звонок" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "ИÑходÑщий звонок" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "ФакÑ" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Рабочий" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Мобильный" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Домашний" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Отправить на %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Только что" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Вчера・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d минута" msgstr[1] "%d минуты" msgstr[2] "%d минут" msgstr[3] "%d минуты" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "ÐедоÑтупно" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Групповое Ñообщение" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Ð’Ñ‹: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "И %d другой контакт" msgstr[1] "И %d других" msgstr[2] "И %d других" msgstr[3] "И %d других" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð°Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ð° на %s не активна" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (ОÑталоÑь…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d До полного зарÑда)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d ОÑталоÑÑŒ)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Ответить" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "ОтправлÑть ÑÑылки в веб браузер или по SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Ð¡ÐµÑ€Ð²Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупен" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Открыть в браузере" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/sk.po000066400000000000000000001135171460766671100234470ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-03-22 10:01+0530\n" "PO-Revision-Date: 2022-04-28 22:33+0200\n" "Last-Translator: Jose Riha \n" "Language-Team: \n" "Language: sk\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==1 ? 0 : n>=2 && n<=4 ? 1 : 2);\n" "X-Generator: Poedit 2.2.1\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementácia aplikácie KDE Connect pre prostredie GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Tím GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 #, fuzzy msgid "" "GSConnect is a complete implementation of KDE Connect especially for GNOME " "Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team " "has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" "GSConnect je kompletná implementácia nástroja KDE Connect urÄená " "predovÅ¡etkým pre prostredie GNOME Shell s integráciou správcu súborov " "Nautilus a internetové prehliadaÄe Chrome a Firefox. Tím KDE Connect " "vytvoril aplikácie pre Linux, BSD, Android, Sailfish, macOS a Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "" "With GSConnect you can securely connect to mobile devices and other desktops " "to:" msgstr "" "VÄaka GSConnect-u sa môžete bezpeÄne pripojiÅ¥ k mobilným zariadeniam a " "Äalším stolným poÄítaÄom a:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "ZdieľaÅ¥ súbory, prepojenia a text" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "PosielaÅ¥ a prijímaÅ¥ správy" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "SynchronizovaÅ¥ obsah schránky" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "SynchronizovaÅ¥ kontakty" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "SynchronizovaÅ¥ oznámenia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "OvládaÅ¥ multimediálne prehrávaÄe" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "OvládaÅ¥ systémovú hlasitosÅ¥" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "SpustiÅ¥ predpripravené príkazy" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "A viac…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:127 msgid "GSConnect in GNOME Shell" msgstr "GSConnect v GNOME Shelli" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "PripojiÅ¥ k…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "ZruÅ¡iÅ¥" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "PripojiÅ¥" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP adresa" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Žiadne kontakty" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Pomocník" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Zadajte telefónne Äíslo alebo meno" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Iné" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "OdoslaÅ¥ SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "OdoslaÅ¥" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Zariadenie je odpojené" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "OdoslaÅ¥ správu" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Zadajte správu" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Zadanie správy" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Zadajte správu a stlaÄením klávesu Enter ju odoÅ¡lite" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Písanie správ" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Nová konverzácia" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Žiadna konverzácia" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nie je vybraná konverzácia" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Vyberte alebo zaÄnite novú konverzáciu" #: data/ui/mousepad-input-dialog.ui:97 msgid "" "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n" "\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "UpraviÅ¥ príkaz" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "UložiÅ¥" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Názov" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Príkazový riadok" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Zvolí spustiteľný súbor" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "OtvoriÅ¥" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Stolný poÄítaÄ" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Fotoaparát" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Synchronizácia schránky" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Multimediálne prehrávaÄe" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "MyÅ¡ a klávesnica" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Ovládanie hlasitosti" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Súbory" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "PrijímaÅ¥ súbory" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "UkladaÅ¥ súbory do" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Zdieľanie" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Stav batérie" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Upozornenie na slabú batériu" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Oznámenie o nabití na nastavenú úroveň" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Oznámenie o plnom nabití" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Systémová batéria" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "ZdieľaÅ¥ Å¡tatistiky" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Batéria" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Príkazy" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "PridaÅ¥ príkaz" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "ZdieľaÅ¥ oznámenia" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "ZdieľaÅ¥, keÄ je aktívny" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Aplikácie" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Oznámenia" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontakty" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Prichádzajúce hovory" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "HlasitosÅ¥" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "PozastaviÅ¥ multimédiá" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Odchádzajúce hovory" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "StíšiÅ¥ mikrofón" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefonovanie" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Skratky akcií" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "ObnoviÅ¥ vÅ¡etko…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Skratky" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Zásuvné moduly" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimentálne" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Vyrovnávacia pamäť zariadenia" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "VyÄistiÅ¥ vyrovnávaciu pamäť…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Podpora SMS (zastarané)" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "Automatické pripojenie SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "PokroÄilé" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Klávesové skratky" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Nastavenie zariadenia" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "SpárovaÅ¥" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Spárovanie so zariadením bolo zruÅ¡ené" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Pred spárovaním môžete toto zariadenie nastaviÅ¥" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Informácie o Å¡ifrovaní" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "ZruÅ¡iÅ¥ párovanie" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Do zariadenia" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Zo zariadenia" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Bez zmeny" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "ObnoviÅ¥" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "ZnížiÅ¥" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "StíšiÅ¥" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "NastaviÅ¥" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" "StlaÄte klávesu Esc na zruÅ¡enie alebo klávesu Backspace na obnovenie " "klávesovej skratky." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Meno zariadenia" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_PremenovaÅ¥" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "ObnoviÅ¥" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobilné nastavenia" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Ponuka služieb" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Ponuka zariadenia" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "UpraviÅ¥ meno zariadenia" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Zariadenia" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Hľadajú sa zariadenia…" #: data/ui/preferences-window.ui:353 msgid "Behavior When Locked" msgstr "" #: data/ui/preferences-window.ui:378 msgid "Keep Alive" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Doplnky prehliadaÄov" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "PovoliÅ¥" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Toto zariadenie nie je viditeľné pre nespárované zariadenia" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Objavenie zakázané" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Režim zobrazenia" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Užívateľská ponuka" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "VytvoriÅ¥ záznam pre technickú podporu" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "O programe GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Výber zariadenia" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "VybraÅ¥" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "NenaÅ¡lo sa žiadne zariadenie" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Zoznam zariadení" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "OhlásiÅ¥" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "NieÄo sa pokazilo" #: data/ui/service-error-dialog.ui:91 msgid "" "GSConnect encountered an unexpected error. Please report the problem and " "include any information that may help." msgstr "" "GSConnect narazil na neoÄakávanú chybu. Prosím, nahláste tento problém a " "priložte vÅ¡etky informácie, ktoré môžu byÅ¥ užitoÄné." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Technické podrobnosti" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "OdoslaÅ¥ do mobilného zariadenia" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "Pripojené" msgstr[1] "Pripojené" msgstr[2] "Pripojené" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "UpraviÅ¥" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "OdstrániÅ¥" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Zakázané" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Zadajte novú skratku pre zmenu akcie %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "Klávesová skratka %s sa už používa" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Kompletná implementácia aplikácie KDE Connect pre prostredie GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "DuÅ¡an Kazik , Jose Riha " #: src/preferences/service.js:418 msgid "" "Debug messages are being logged. Take any steps necessary to reproduce a " "problem then review the log." msgstr "" "Správy ladenia sú zaznamenávané. Pokúste sa opätovne vyvolaÅ¥ problém a " "pozrite sa na súbor so záznamom." #: src/preferences/service.js:421 msgid "Review Log" msgstr "PrezrieÅ¥ súbor so záznamom" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Notebook" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Televízia" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Spárovanie zruÅ¡ené" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Odpojené" #: src/preferences/service.js:525 msgid "Connected" msgstr "Pripojené" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "ÄŒaká sa na službu…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Kliknutím získate pomoc pri rieÅ¡ení problému" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Kliknutím získate viac informácií" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "VytoÄiÅ¥ Äíslo" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "ZdieľaÅ¥ súbor" #: src/service/daemon.js:355 msgid "List available devices" msgstr "ZobraziÅ¥ dostupné zariadenia" #: src/service/daemon.js:364 msgid "List all devices" msgstr "ZobraziÅ¥ vÅ¡etky zariadenia" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Cieľové zariadenie" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Telo správy" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "OdoslaÅ¥ oznámenie" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Meno aplikácie v oznámení" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Telo oznámenia" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Ikona oznámenia" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID oznámenia" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Fotografia" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "PrezvoniÅ¥" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "ZdieľaÅ¥ odkaz" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "ZdieľaÅ¥ text" #: src/service/daemon.js:532 msgid "Show release version" msgstr "ZobraziÅ¥ verziu vydania" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Zariadenie Bluetooth na adrese %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, fuzzy, javascript-format msgid "Verification key: %s" msgstr "Oznámenia" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Požiadavka na spárovanie od zariadenia %s" #: src/service/device.js:850 msgid "Reject" msgstr "OdmietnuÅ¥" #: src/service/device.js:855 msgid "Accept" msgstr "PrijaÅ¥" #: src/service/manager.js:118 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "Objavenie bolo zakázané kvôli poÄtu zariadení na tejto sieti." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL nebolo nájdené" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Port už sa používa" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "VymieňaÅ¥ informácie o batérii" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Batéria je plne nabitá" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Plne nabitá" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Batéria dosiahla nastavenú úroveň nabitia" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% nabité" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Batéria je takmer vybitá" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "Zostáva %d%%" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Schránka" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "ZdieľaÅ¥ obsah schránky" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "OdoslaÅ¥ obsah schránky" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "PrijaÅ¥ obsah schránky" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "PristupovaÅ¥ ku kontaktom na spárovanom zariadení" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Nájdi môj telefón" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Prezvoní vaÅ¡e spárované zariadenie" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Ovládanie myÅ¡i" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Umožní použiÅ¥ spárované zariadenie ako vzdialenú myÅ¡ a klávesnicu" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Obojsmerné diaľkové ovládanie prehrávania multimédií" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Neznáme" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "ZdieľaÅ¥ oznámenia so spárovaným zariadením" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "ZruÅ¡iÅ¥ oznámenie" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "ZavrieÅ¥ oznámenie" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "OdpovedaÅ¥ na oznámenie" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "AktivovaÅ¥ oznámenia" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" "PožiadaÅ¥ spárované zariadenie o vytvorenie fotografie a jej prenos do tohto " "poÄítaÄa" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Prenos zlyhal" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Zlyhalo odoslanie súboru „%s“ do zariadenia %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "OdoslaÅ¥ a prijímaÅ¥ pingy" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Prezentácia" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "PoužiÅ¥ spárované zariadenie ako prezentátor" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "SpustiÅ¥ príkazy" #: src/service/plugins/runcommand.js:17 msgid "" "Run commands on your paired device or let the device run predefined commands " "on this PC" msgstr "" "SpustiÅ¥ príkazy na vaÅ¡om spárovanom zariadení alebo umožniÅ¥ zariadeniu " "spúšťaÅ¥ predpripravené príkazy na tomto poÄítaÄi" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "PrehliadaÅ¥ súborový systém spárovaného zariadenia" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "PripojiÅ¥" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "OdpojiÅ¥" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s nahlásilo chybu" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "ZdieľaÅ¥" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "ZdieľaÅ¥ súbory a URL adresy medzi zariadeniami" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s nemá povolené nahrávanie súborov" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Prebieha prenos súboru" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Prijíma sa „%s“ zo zariadenia %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Prenos úspeÅ¡ný" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Prijatý súbor „%s“ zo zariadenia %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "OtvoriÅ¥ súbor" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Zlyhalo prijatie súboru „%s“ zo zariadenia %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Text zdieľaný zariadením %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Odosiela sa súbor „%s“ do zariadenia %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Odoslaný súbor „%s“ do zariadenia %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Odoslanie súborov do zariadenia %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "OtvoriÅ¥ po dokonÄení" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "OdoÅ¡le odkaz do zariadenia %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" "OdoslaÅ¥ a ÄítaÅ¥ SMS správy zo spárovaného zariadenia a dostávaÅ¥ upozornenia " "na nové správy" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nová SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "OdpovedaÅ¥ na SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "ZdieľaÅ¥ SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Systémová hlasitosÅ¥" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "PovoliÅ¥ spárovanému zariadeniu ovládaÅ¥ systémovú hlasitosÅ¥" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio nebolo nájdené" #: src/service/plugins/telephony.js:18 msgid "" "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" "DostávaÅ¥ upozornenia na hovory a upravovaÅ¥ hlasitosÅ¥ poÄas zvonenia/" "prebiehajúceho hovoru" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "StíšiÅ¥ hovor" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Neznámy kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Prichádzajúci hovor" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Odchádzajúci hovor" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Práca" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Domov" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "OdoslaÅ¥ na Äíslo %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Práve teraz" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "VÄera・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minúta" msgstr[1] "%d minúty" msgstr[2] "%d minút" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Nedostupný" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Skupinová správa" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Vy: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "A jeden Äalší kontakt" msgstr[1] "A %d ÄalÅ¡ie kontakty" msgstr[2] "A %d Äalších kontaktov" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Vzdialená klávesnica na %s nie je aktívna" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Odhaduje sa…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d do plného nabitia)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Zostáva %d∶%02d)" #: src/shell/notification.js:58 msgid "Reply" msgstr "OdpovedaÅ¥" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "" "Zdieľanie odkazov s aplikáciou GSConnect, priamo cez prehliadaÄ alebo formou " "SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Služba nedostupná" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "OtvoriÅ¥ v prehliadaÄi" #~ msgid "Add" #~ msgstr "PridaÅ¥" #~ msgid "On" #~ msgstr "Zapnuté" #~ msgid "Off" #~ msgstr "Vypnuté" #~ msgid "Set Shortcut" #~ msgstr "Nastavenie skratky" #~ msgid "Authentication Failure" #~ msgstr "Zlyhalo overenie totožnosti" #~ msgid "Network Error" #~ msgstr "SieÅ¥ová chyba" #~ msgid "Keyboard not ready" #~ msgstr "Klávesnica nie je pripravená" #~ msgid "All files" #~ msgstr "VÅ¡etky súbory" #~ msgid "Camera pictures" #~ msgstr "Snímky fotoaparátu" #, javascript-format #~ msgid "%d hour" #~ msgid_plural "%d hours" #~ msgstr[0] "%d hodina" #~ msgstr[1] "%d hodiny" #~ msgstr[2] "%d hodín" #, javascript-format #~ msgid "Until %s (%s)" #~ msgstr "Do %s (%s)" #~ msgid "Do Not Disturb" #~ msgstr "NevyruÅ¡ovaÅ¥" #~ msgid "Until you turn off Do Not Disturb" #~ msgstr "Pokiaľ nevypnete funkciu NeruÅ¡iÅ¥" #~ msgid "Done" #~ msgstr "Hotovo" #~ msgid "Command Shortcuts" #~ msgstr "Skratky príkazov" #~ msgid "Delete" #~ msgstr "OdstrániÅ¥" #~ msgid "Delete this device" #~ msgstr "Odstránenie tohoto zariadenia" #~ msgid "Unpair and remove all settings and files" #~ msgstr "Zruší párovanie a odstráni vÅ¡etky nastavenia a súbory" #~ msgid "Debugger" #~ msgstr "Ladiaci nástroj" #~ msgid "About" #~ msgstr "O aplikácii" #~ msgid "Switch to Bluetooth" #~ msgstr "Prepnúť na Bluetooth" #~ msgid "Switch to LAN" #~ msgstr "Prepnúť na LAN" #~ msgid "Appearance" #~ msgstr "Vzhľad" #~ msgid "Discoverable" #~ msgstr "Objaviteľná" #~ msgid "Restart Service" #~ msgstr "ReÅ¡tartovaÅ¥ službu" #~ msgid "Settings" #~ msgstr "Nastavenia" #~ msgid "Remote Filesystems" #~ msgstr "Vzdialené súborové systémy" #~ msgid "Extended Keyboard Support" #~ msgstr "Rozšírená podpora klávesnice" #~ msgid "Additional Features" #~ msgstr "DodatoÄné funkcie" #~ msgid "KDE Connect" #~ msgstr "KDE Connect" #~ msgid "Click to open preferences" #~ msgstr "Kliknutím otvoríte nastavenia" #~ msgid "%s Plugin Failed To Load" #~ msgstr "Zlyhalo naÄítanie zásuvného modulu %s" #~ msgid "GSConnect: %s" #~ msgstr "GSConnect: %s" #~ msgid "Reconnect" #~ msgstr "Znovu pripojiÅ¥" #~ msgid "Additional Software Required" #~ msgstr "Vyžaduje sa dodatoÄný softvér" #~ msgid "Starting Transfer" #~ msgstr "Spúšťa sa prenos" #~ msgid "Select a contact or number" #~ msgstr "Vyberte kontakt alebo Äíslo" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/sr.po000066400000000000000000001006301460766671100234460ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Serbian (Cyrillic)\n" "Language: sr_RS\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: sr\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Повежи Ñе Ñа…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Откажи" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Повежи" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Помоћ" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Унеите број телефона или име" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Пошаљи СМС" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Пошаљи" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Уређај није повезан" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "УнеÑите поруку" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Поруке" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Име" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Командна линија" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Изаберите извршни фајл" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Отвори" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Радна површ" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Синхронизација оÑтаве" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "МедијÑки плејери" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Миш и таÑтатура" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Јачина звука" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Фајлови" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Дељење" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Батерија" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Ðаредбе" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Дели обавештења" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Програми" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Обавештења" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Контакти" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Долазни позиви" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Јачина" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Паузирај медије" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Текућии позиви" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Утишај микрофон" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Телефонија" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Пречице радњи" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "РеÑетуј Ñве…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Пречице" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Прикључци" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Ðапредно" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Пречице таÑтатуре" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Упари" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "РаÑпари" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Ðа уређај" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Са уређаја" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Ðишта" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Утишај" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Утишај" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "ПоÑтави" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "ПритиÑните ЕÑц да откажете или Повратник да реÑетујете пречицу таÑтатуре." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "ОÑвежи" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Подешавање" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Додаци за прегледаче" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Откривање је онемогућено" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Режим приказа" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Панел" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "КориÑнички мени" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Изаберите уређај" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Изаберите" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Ðема нађених уређаја" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Пријави" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "" #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Пошаљи на мобилни уређај" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Онемогућен" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "УнеÑите нову пречицу да замените %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s је Ñпреман за употребу" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Потпуна имплементација КДЕ Конекта за Гном" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Слободан Терзић (githzerai06@gmail.com)" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "" #: src/preferences/service.js:421 msgid "Review Log" msgstr "" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Лаптоп" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Паметни телефон" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Таблет" #: src/preferences/service.js:495 msgid "Television" msgstr "" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "" #: src/preferences/service.js:525 msgid "Connected" msgstr "" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Бирај број" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Дели фајл" #: src/service/daemon.js:355 msgid "List available devices" msgstr "" #: src/service/daemon.js:364 msgid "List all devices" msgstr "" #: src/service/daemon.js:373 msgid "Target Device" msgstr "" #: src/service/daemon.js:415 msgid "Message Body" msgstr "" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Пошаљи обавештење" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Пинг" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Подели везу" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Дели текÑÑ‚" #: src/service/daemon.js:532 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Захтев за упаривање од %s" #: src/service/device.js:850 msgid "Reject" msgstr "Одбиј" #: src/service/device.js:855 msgid "Accept" msgstr "Прихвати" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Откривање је онемогућено уÑлед броја уређаја у овој мрежи." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Потпуно пуна" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: батерија је при крају" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% преоÑтаје" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "ОÑтава" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Слање у оÑтаву" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Довлачење из оÑтаве" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Ðађи ми телефон" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Подлога за миша" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "МПРИС" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Откажи обавештење" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Затвори обавештење" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Обавештење о оддговору" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "ÐŸÑ€ÐµÐ½Ð¾Ñ Ð½Ð¸Ñ˜Ðµ уÑпео" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "ÐеуÑпело Ñлање „%s“ на %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Пинг: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Извршавање нареби" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "СФТП" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Монтирај" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Демонтирај" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Дељење" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Примам „%s“ од %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "УÑпешан преноÑ" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Примих „%s“ од %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Отвори фајл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "ÐеуÑпешан пријем „%s“ од %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Дељени текÑÑ‚ од %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Слање „%s“ за %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "ПоÑлах „%s“ за %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Пошаљи фајлове на %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Пошаљи везе на %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "СМС" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Ðови СМС (УРИ)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Одговори на СМС" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Дели СМС" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "СиÑтемÑка јачина" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Утишај позив" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Ðепознат контакт" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Долазни позив" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Текући позив" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Пошаљи на %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Управо Ñада" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Јуче・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Ðије доÑтупно" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "" msgstr[1] "" msgstr[2] "" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Процењујем…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d до пуне)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d преоÑтаје)" #: src/shell/notification.js:58 msgid "Reply" msgstr "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Дели везе ГСКонектом, директно у преглдач или путем СМС-а." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Ð¡ÐµÑ€Ð²Ð¸Ñ Ð½Ð¸Ñ˜Ðµ доÑтупан" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Отвори у прегледачу" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/sr@latin.po000066400000000000000000001006731460766671100246050ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Serbian (Latin)\n" "Language: sr@latin\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: sr-CS\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Potpuna implementacija KDE Konekta za Gnom" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Nema kontakata" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "PoÅ¡alji obaveÅ¡tenje" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Medijski plejeri" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Sistemska jaÄina" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Poveži se sa…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Otkaži" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Poveži" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP adresa" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Nema kontakata" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Pomoć" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Uneite broj telefona ili ime" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Drugo" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "PoÅ¡alji SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "PoÅ¡alji" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "UreÄ‘aj nije povezan" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "PoÅ¡alji poruku" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Unesite poruku" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Nova poruka" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Poruke" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Novi razgovor" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Nema razgovora" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Nije izabran razgovor" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Izaberite ili zapoÄnite razgovor" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Naredbe" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Snimi" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Ime" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Komandna linija" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Izaberite izvrÅ¡ni fajl" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Otvori" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Radna povrÅ¡" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Sinhronizacija ostave" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Medijski plejeri" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "MiÅ¡ i tastatura" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "JaÄina zvuka" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Fajlovi" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Primi fajlove" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "PoÅ¡alji fajlove na" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Deljenje" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Батерија" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Ðаредбе" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Дели обавештења" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Програми" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Обавештења" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Контакти" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Долазни позиви" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Јачина" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Паузирај медије" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Текућии позиви" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Утишај микрофон" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Телефонија" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Пречице радњи" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "РеÑетуј Ñве…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Пречице" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Прикључци" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Ðапредно" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Пречице таÑтатуре" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Упари" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "РаÑпари" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Ðа уређај" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Са уређаја" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Ðишта" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Утишај" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Утишај" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "ПоÑтави" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "ПритиÑните ЕÑц да откажете или Повратник да реÑетујете пречицу таÑтатуре." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Preimenuj" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Osveži" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "PodeÅ¡avanje" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Servis" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Naziv ureÄ‘aja" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Uredi naziv ureÄ‘aja" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "UreÄ‘aji" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Tražim ureÄ‘aje…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Dodaci za pregledaÄe" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Omogući" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Ovaj ureÄ‘aj je nevidljiv neuparenim ureÄ‘ajima" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Otkrivanje je onemogućeno" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Režim prikaza" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "KorisniÄki meni" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Napravi dnevnik za podrÅ¡ku" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "O programu" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Izaberite ureÄ‘aj" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Izaberite" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Nema naÄ‘enih ureÄ‘aja" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "UreÄ‘aji" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Prijavi" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "" #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "TehniÄki detalјi" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "PoÅ¡alji na mobilni ureÄ‘aj" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Uredi" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Ukloni" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Onemogućen" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Unesite novu preÄicu da zamenite %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s je spreman za upotrebu" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Potpuna implementacija KDE Konekta za Gnom" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Slobodan Terzić (githzerai06@gmail.com)" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "" #: src/preferences/service.js:421 msgid "Review Log" msgstr "" #: src/preferences/service.js:489 msgid "Laptop" msgstr "" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Pametni telefon" #: src/preferences/service.js:493 msgid "Tablet" msgstr "" #: src/preferences/service.js:495 msgid "Television" msgstr "" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Nepovezan" #: src/preferences/service.js:525 msgid "Connected" msgstr "Povezan" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "ÄŒekam na servis…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Kliknite za pomoć u otklanjanju" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Kliknite za viÅ¡e informacija" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Biraj broj" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Deli fajl" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Nije dostupno" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Mobilni ureÄ‘aji" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Na ureÄ‘aj" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Tijelo poruke" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "PoÅ¡alji obaveÅ¡tenje" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "ObaveÅ¡tenja" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID broj obaveÅ¡tenja" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Fotografija" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Pozvoni" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Podeli vezu" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Deli tekst" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Dodajte osobe da zapoÄnete razgovor" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Blutut ureÄ‘aj na %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Zahtev za uparivanje od %s" #: src/service/device.js:850 msgid "Reject" msgstr "Odbij" #: src/service/device.js:855 msgid "Accept" msgstr "Prihvati" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Otkrivanje je onemogućeno usled broja ureÄ‘aja u ovoj mreži." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "%s je spreman za upotrebu" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: baterija je pri kraju" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Potpuno puna" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: baterija je pri kraju" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% preostaje" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Ostava" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Слање у оÑтаву" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Довлачење из оÑтаве" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Ðађи ми телефон" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Подлога за миша" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "МПРИС" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Откажи обавештење" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Затвори обавештење" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Обавештење о оддговору" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "ÐŸÑ€ÐµÐ½Ð¾Ñ Ð½Ð¸Ñ˜Ðµ уÑпео" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "ÐеуÑпело Ñлање „%s“ на %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Извршавање нареби" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "СФТП" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Монтирај" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Демонтирај" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Дељење" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Примам „%s“ од %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "УÑпешан преноÑ" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Примих „%s“ од %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Отвори фајл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "ÐеуÑпешан пријем „%s“ од %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Дељени текÑÑ‚ од %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Слање „%s“ за %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "ПоÑлах „%s“ за %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Пошаљи фајлове на %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Пошаљи везе на %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "СМС" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Ðови СМС (УРИ)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Одговори на СМС" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Дели СМС" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "СиÑтемÑка јачина" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Утишај позив" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Ðепознат контакт" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Долазни позив" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Текући позив" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Пошаљи на %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Управо Ñада" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Јуче・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Nije dostupno" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "" msgstr[1] "" msgstr[2] "" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Procenjujem…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d do pune)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d preostaje)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Odgovori" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Deli veze GSKonektom, direktno u pregldaÄ ili putem SMS-a." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Servis nije dostupan" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Otvori u pregledaÄu" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/sv.po000066400000000000000000001044161460766671100234600ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Swedish\n" "Language: sv_SE\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: sv-SE\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Implementering av KDE Connect för GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect-gruppen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect är en komplett implementering av KDE Connect speciellt för GNOME Shell med Nautilus, Chrome och Firefox integration. KDE Connect-teamet har applikation för Linux, BSD, Android, Sailfish, iOS, macOS och Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "Med GSConnect kan du säkert ansluta till mobila enheter och andra skrivbord till:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Dela filer, länkar och text" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Skicka och ta emot meddelanden" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Synkronisera urklippets innehÃ¥ll" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Synkronisera kontakter" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Synkronisera aviseringar" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Styr mediaspelare" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Styr systemets volym" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Kör fördefinierade kommandon" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Och mer…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect i GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Anslut till…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "Avbryt" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Anslut" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "Ip-adress" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Inga kontakter" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Hjälp" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Skriv in ett telefonnummer eller namn" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Övrigt" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Skicka SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Skicka" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Enheten är frÃ¥nkopplad" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Skicka meddelande" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Skriv ett meddelande" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Meddelandepost" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Skriv ett meddelande och tryck pÃ¥ Enter för att skicka" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "Meddelanden" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Ny konversation" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Inga konversationer" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Ingen konversation vald" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Välj eller starta en konversation" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Redigera kommando" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Spara" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Namn" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Kommandorad" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Välj en körbar fil" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Öppna" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Skrivbord" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Urklippssynkronisering" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Mediaspelare" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Mus & tangentbord" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Volymkontroll" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Filer" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Ta emot filer" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Spara filer till" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Delning" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Batteri för enhet" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Avisering vid lÃ¥g batterinivÃ¥" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Avisering när uppladdning nÃ¥r inställd nivÃ¥" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Avisering när fulladdad" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Systemets batteri" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "Dela statistik" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Batteri" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Kommandon" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Lägg till kommando" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Dela aviseringar" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Dela när aktiv" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Program" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Aviseringar" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Kontakter" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Inkommande samtal" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Volym" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Pausa media" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "PÃ¥gÃ¥ende samtal" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Stäng av mikrofonen" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefon" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Genvägar för Ã¥tgärd" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Ã…terställ alla…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Genvägar" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Insticksmoduler" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Experimentellt" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Enhetscache" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "Rensa cache…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Äldre SMS-stöd" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFTP-automontering" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Avancerat" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Tangentbordsgenvägar" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Enhetsinställningar" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Para ihop" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Enheten är oparad" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Du kan konfigurera den här enheten innan ihopparning" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Krypteringsinformation" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Ta bort ihopparning" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Till enhet" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "FrÃ¥n enhet" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Inget" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Ã…terställ" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Sänk" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Stäng av ljud" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Ange" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Tryck pÃ¥ Esc för att avbryta eller Backspace för att Ã¥terställa tangentbordsgenvägen." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Enhetsnamn" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Ändra namn" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Uppdatera" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobilinställningar" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Servicemeny" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Enhetsmeny" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Redigera enhetsnamn" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Enheter" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Söker efter enheter…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Webbläsartillägg" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Aktivera" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Den här enheten är osynlig för enheter som inte är ihopparade" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "Upptäckt inaktiverat" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Visningsläge" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Användarmeny" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Skapa supportlogg" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Om GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Välj en enhet" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Välj" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Ingen enhet hittades" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Enhetslista" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Rapport" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "NÃ¥got gick fel" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect stötte pÃ¥ ett oväntat fel. Rapportera problemet och inkludera all information som kan hjälpa." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Teknisk information" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Skicka till mobil enhet" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d ansluten" msgstr[1] "%d anslutna" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Redigera" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Ta bort" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Inaktiverad" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Ange en ny genväg för att ändra %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s används redan" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "En fullständig implementation av KDE Connect för GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Morgan Antonsson " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Felsökningsmeddelanden loggas. Utför de Ã¥tgärder som krävs för att Ã¥terskapa problemet och granska sedan loggen." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Granska logg" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Bärbar dator" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Surfplatta" #: src/preferences/service.js:495 msgid "Television" msgstr "TV" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Inte ihopparad" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Bortkopplad" #: src/preferences/service.js:525 msgid "Connected" msgstr "Ansluten" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Väntar pÃ¥ tjänst…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Klicka för att fÃ¥ hjälp med felsökning" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Klicka här för mer information" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "SlÃ¥ nummer" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Dela fil" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Visa tillgängliga enheter" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Visa alla enheter" #: src/service/daemon.js:373 msgid "Target Device" msgstr "MÃ¥lenhet" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Meddelande-text" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Skicka avisering" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Namn pÃ¥ aviseringsapp" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Aviseringstext" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Aviseringsikon" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Aviserings-ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Foto" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Ring" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "Dela länk" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Dela text" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Visa utgÃ¥vans version" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-enhet pÃ¥ %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Begäran om ihopparning frÃ¥n %s" #: src/service/device.js:850 msgid "Reject" msgstr "Avvisa" #: src/service/device.js:855 msgid "Accept" msgstr "Acceptera" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Upptäckt har inaktiverats pÃ¥ grund av antalet enheter pÃ¥ detta nätverk." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL kunde inte hittas" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Porten används redan" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Utbyt batteriinformation" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Batteriet är fullt" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Fulladdad" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: Batteriet har nÃ¥tt anpassad laddningsnivÃ¥" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% laddat" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: LÃ¥g batterinivÃ¥" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% kvar" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Urklipp" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "Dela urklippets innehÃ¥ll" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Skicka urklipp" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Hämta urklipp" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "FÃ¥ tillgÃ¥ng till kontakter frÃ¥n den parkopplade enheten" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Hitta min telefon" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Ring din ihopparade enhet" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Musmatta" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Gör det möjligt för den ihopparade enheten att fungera som en fjärrmus och tangentbord" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "Dubbelriktad fjärrstyrning för medieuppspelning" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Okänd" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "Dela aviseringar med den ihopparade enheten" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Avbryt avisering" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Stäng avisering" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Svara pÃ¥ avisering" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Aktivera avisering" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Be den ihopparade enheten att ta ett foto och överföra det till den här datorn" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Överföring misslyckades" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Det gick inte att skicka â€%s†till %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "Skicka och ta emot pingar" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Presentation" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "Använd den ihopparade enheten som presentatör" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Kör kommandon" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Kör kommandon pÃ¥ din ihopparade enhet eller lÃ¥t enheten köra fördefinierade kommandon pÃ¥ den här datorn" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "Bläddra i den ihopparade enhetens filsystem" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Montera" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Avmontera" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s rapporterade ett felmeddelande" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "Dela" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "Dela filer och webbadresser mellan enheter" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s fÃ¥r inte ladda upp filer" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Överför fil" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Tar emot â€%s†frÃ¥n %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Överföringen klar" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Tog emot â€%s†frÃ¥n %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Öppna fil" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Det gick inte att ta emot â€%s†frÃ¥n %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Text delad av %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Skickar â€%s†till %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Skickade â€%s†till %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Skicka filer till %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Öppna när det är klart" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Skicka en länk till %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Skicka och läs SMS frÃ¥n den ihopparade enheten och bli meddelad om nya SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Nytt SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "Svara SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "Dela SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Systemvolym" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Gör det möjligt för den ihopparade enheten att styra systemets volym" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio kunde inte hittas" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "FÃ¥ meddelande om samtal och justera systemets volym vid ringande/pÃ¥gÃ¥ende samtal" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Tysta samtal" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Okänd kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Inkommande samtal" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "PÃ¥gÃ¥ende samtal" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Fax" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Arbete" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Hem" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Skicka till %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Precis nu" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "IgÃ¥r・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minuter" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Inte tillgänglig" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Gruppmeddelande" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Du: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Och %d annan kontakt" msgstr[1] "Och %d andra" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Fjärrtangentbordet pÃ¥ %s är inte aktivt" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (beräknar…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d:%02d tills fullt)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d kvar)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Svara" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Dela länkar med GSConnect, direkt till webbläsaren eller med SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Tjänsten är inte tillgänglig" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Öppna i webbläsare" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/tr.po000066400000000000000000001036421460766671100234550ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-30 16:05\n" "Last-Translator: \n" "Language-Team: Turkish\n" "Language: tr_TR\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: tr\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "GNOME için KDE Connect uygulaması" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect Takımı" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect, Nautilus, Chrome ve Firefox bütünleÅŸmesi ile özellikle GNOME KabuÄŸu için eksiksiz bir KDE Connect uygulamasıdır. KDE Connect ekibinin Linux, BSD, Android, Sailfish, iOS, macOS ve Windows için uygulamaları vardır." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "GSConnect ile mobil cihazlara ve diÄŸer masaüstlerine baÄŸlanarak ÅŸunları yapabilirsiniz:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "Dosyaları, baÄŸlantıları ve metni paylaÅŸ" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "Mesaj gönder ve al" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Pano içeriÄŸini eÅŸitle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "KiÅŸileri eÅŸitle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Bildirimleri eÅŸitle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Medya oynatıcıları kontrol et" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Sistem sesini kontrol et" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Ön tanımlı komutları yürüt" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Ve daha fazlası…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GNOME KabuÄŸunda GSConnect" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "BaÄŸlan…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "İptal" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "BaÄŸlan" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP Adresi" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "KiÅŸi yok" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Yardım" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Telefon numarası veya isim yaz" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "DiÄŸer" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "SMS Gönder" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Gönder" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "Cihaz çevrim dışı" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Mesaj Gönder" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Bir Mesaj Yaz" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Mesaj Girdisi" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Bir mesaj yazın ve göndermek için Enter tuÅŸuna basın" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "MesajlaÅŸma" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Yeni Sohbet" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Sohbet yok" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Hiçbir sohbet seçilmedi" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Sohbet seç ya da baÅŸlat" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Dokunmatik yüzey.\n" "Fare imlecini hareket ettirmek için bu alanda sürükleyin.\n" "Fare imlecini bi yerden bi yere sürüklemek için uzun basın.\n\n" "EÅŸleÅŸtirilmiÅŸ cihaza basit bir tıklama gönderilecektir.\n" "Sol, orta, saÄŸ düğme ve imleç kaydırmaları." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Komutu Düzenle" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Kaydet" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "İsim" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Komut Satırı" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Yürütülebilir dosya seç" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Aç" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "Masaüstü" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Kamera" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Pano EÅŸleÅŸmesi" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Medya Oynatıcılar" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Fare & Klavye" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "Ses Kontrolü" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Dosyalar" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Alınan Dosyalar" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Dosyaları ÅŸuraya kaydet" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "Paylaşım" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "Cihaz Bataryası" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Düşük Batarya Bildirimi" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Tam Åžarj Bildirimi" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "Sistem Bataryası" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "İstatistikleri PaylaÅŸ" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "Batarya" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Komutlar" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Komut Ekle" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "Bildirimleri PaylaÅŸ" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "Aktif OlduÄŸunda PaylaÅŸ" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Uygulamalar" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "Bildirimler" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "KiÅŸiler" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Gelen ÇaÄŸrılar" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "Ses" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Medyayı Duraklat" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Devam Eden ÇaÄŸrılar" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Mikrofonu Sustur" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "Telefon" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Kısayol Eylemleri" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Tümünü Sıfırla…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "Kısayollar" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Eklentiler" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "Deneysel" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Cihaz ÖnbelleÄŸi" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "ÖnbelleÄŸi Temizle…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "Eski SMS DesteÄŸi" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "SFTP Otomatik BaÄŸla" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "GeliÅŸmiÅŸ" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Klavye Kısayolları" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "Cihaz Ayarları" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "EÅŸleÅŸtir" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "Cihaz eÅŸleÅŸmemiÅŸ" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "EÅŸleÅŸmeden önce cihazı yapılandır" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Åžifreleme Bilgisi" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "EÅŸleÅŸtirmeyi Bitir" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "Cihaza" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Cihazdan" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Hiç Biri" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Geri yükle" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Düşük" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Sessiz" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Ayarla" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Klavye kısayolunu sıfırlamak için iptal, geri almak için ESC tuÅŸuna bas." #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Cihaz Adı" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Adlandır" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Yenile" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Mobil Ayarları" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "Servis Menüsü" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Cihaz Menüsü" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Cihaz Adını Düzenle" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "Cihazlar" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Cihazlar aranıyor…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Eklenti Ayarları" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GNOME kabuÄŸu kilitlendiÄŸinde GSConnect etkin kalır" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Tarayıcı Eklentileri" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Etkin" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Bu cihaz eÅŸleÅŸtirme yapılmayan cihazlara görünmez" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "KeÅŸif Devre Dışı" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Görünüm Kipi" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Sistem Paneli" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Kullanıcı Menüsü" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Destek Günlüğü OluÅŸtur" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "GSConnect Hakkında" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Cihaz Seç" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Seç" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "Cihaz Bulunamadı" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Cihaz Listesi" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Rapor" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "Bir ÅŸeyler ters gitti" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect beklenmedik bir hatayla karşılaÅŸtı. Lütfen sorunu bildirin ve yardımcı olabilecek tüm bilgileri ekleyin." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Teknik Ayrıntılar" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Mobil Cihaza Gönder" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Cihazlarınız arasında eÅŸitleyin" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d BaÄŸlı" msgstr[1] "%d BaÄŸlı" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Düzenle" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Kaldır" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Devre dışı" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "DeÄŸiÅŸtirmek için yeni bir kısayol gir %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s zaten kullanılıyor" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "GNOME için eksiksiz bir KDE Connect uygulaması" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "Serdar SaÄŸlam \n" "Orhan Engin Okay \n" "A. Burak TektaÅŸ " #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Hata ayıklama mesajları kaydediliyor. Sorunu yeniden oluÅŸturmak için gerekli adımları izleyin ve günlüğü inceleyin." #: src/preferences/service.js:421 msgid "Review Log" msgstr "Günlüğü Gözden Geçir" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Dizüstü" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Telefon" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:495 msgid "Television" msgstr "Televizyon" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "EÅŸleÅŸmemiÅŸ" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "BaÄŸlantı kesildi" #: src/preferences/service.js:525 msgid "Connected" msgstr "BaÄŸlı" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "Hizmet bekleniyor…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "Sorun giderme konusunda yardım için tıkla" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "Daha fazla bilgi için tıkla" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Numarayı Çevir" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "Dosya Paylaşımı" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Kullanılabilir cihazları listele" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Tüm cihazları listele" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Hedef Cihaz" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Mesaj Metni" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Bildirim Gönder" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Bildirim Uygulama Adı" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Bildirim Metni" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Bildirim Simgesi" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "Bildirim KimliÄŸi" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "FotoÄŸraf" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Yokla" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Çaldır" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "BaÄŸlantıyı PaylaÅŸ" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "Metni PaylaÅŸ" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Sürüm versiyonunu göster" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth cihazı %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "DoÄŸrulama anahtarı: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "%s tarafından Gelen EÅŸleÅŸme İsteÄŸi" #: src/service/device.js:850 msgid "Reject" msgstr "Reddet" #: src/service/device.js:855 msgid "Accept" msgstr "Kabul et" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Bu aÄŸdaki cihazların sayısı nedeniyle keÅŸif devre dışı bırakıldı." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL bulunamadı" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "BaÄŸlantı noktası zaten kullanılıyor" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: Batarya dolu" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "Åžarj Oldu" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Düşük batarya" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "%d%% kaldı" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Pano" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Panoya Gönder" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Panodan Al" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Telefonumu Bul" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Fare DesteÄŸi" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Uzak Girdi" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Bilinmeyen" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "Bildirimi İptal Et" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Bildirimi Kapat" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "Bildirimi Yanıtla" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Bildirimi EtkinleÅŸtir" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Aktarım BaÅŸarısız" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Gönderilemedi “%s†hedef %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Yokla: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "Sunum" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Komutları Çalıştır" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "BaÄŸla" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Ayır" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s bir hata bildirdi" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "PaylaÅŸ" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s dosya yükleme iznine sahip deÄŸil" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "Dosya Aktarımı" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "Alınıyor “%s†kaynak %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Aktarım BaÅŸarılı" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Alınan “%s†kaynak %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Dosya Konumunu Göster" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Dosyayı Aç" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Alım baÅŸarısız “%s†kaynak %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "Paylaşılan Metin %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Gönderiliyor “%s†hedef %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "Gönderildi “%s†hedef %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Dosyaları gönder %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Tamamlandığında aç" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "BaÄŸlantı gönder %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Yeni SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "SMS Yanıtla" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "SMS PaylaÅŸ" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "Sistem Sesi" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio bulunamadı" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Sesi Kapat" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Bilinmeyen KiÅŸi" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Gelen çaÄŸrı" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Devam eden çaÄŸrı" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "Faks" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "İş" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Ev" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Gönder %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Hemen ÅŸimdi" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Dün %s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d dakika" msgstr[1] "%d dakika" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "Müsait DeÄŸil" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Grup Mesajı" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Sen: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "Ve %d baÅŸka kiÅŸi" msgstr[1] "Ve %d baÅŸkaları" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "%s üzerindeki uzak klavye etkin deÄŸil" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Tahmini…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (Dolma Süresi %d∶%02d)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Kalan Süre %d∶%02d)" #: src/shell/notification.js:58 msgid "Reply" msgstr "Yanıtla" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "GSConnect ile baÄŸlantıları doÄŸrudan tarayıcı veya SMS ile paylaÅŸ." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Servis Mevcut DeÄŸil" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Tarayıcıda Aç" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/uk.po000066400000000000000000001204621460766671100234460ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 17:36\n" "Last-Translator: \n" "Language-Team: Ukrainian\n" "Language: uk_UA\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%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: uk\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "Ð ÐµÐ°Ð»Ñ–Ð·Ð°Ñ†Ñ–Ñ KDE Connect Ð´Ð»Ñ GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "Команда GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect — це повна Ñ€ÐµÐ°Ð»Ñ–Ð·Ð°Ñ†Ñ–Ñ KDE Connect Ñпеціально Ð´Ð»Ñ GNOME Shell з інтеграцією з Nautilus, Chrome Ñ– Firefox. Команда KDE Connect має заÑтоÑунки Ð´Ð»Ñ Linux, BSD, Android, Sailfish, iOS, macOS Ñ– Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "За допомогою GSConnect можна безпечно з'єднуватиÑÑ Ð· мобільними приÑтроÑми та іншими комп'ютерами, щоб:" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "ДілитиÑÑ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸, поÑиланнÑми Ñ– текÑтом" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "ÐадÑилати й отримувати повідомленнÑ" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "Синхронізувати вміÑÑ‚ буфера обміну" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "Синхронізувати контакти" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "Синхронізувати ÑповіщеннÑ" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "Керувати медіа плеєрами" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "Керувати гучніÑтю ÑиÑтеми" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "Виконувати попередньо визначені команди" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "Та інше…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GSConnect у GNOME Shell" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "Під'єднатиÑÑ Ð´Ð¾â€¦" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "СкаÑувати" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "Під'єднатиÑÑŒ" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP-адреÑа" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "Ðемає контактів" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "Допомога" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "Введіть номер телефону або ім'Ñ" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "Інший" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "Відправити SMS" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "Відправити" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "ПриÑтрій від'єднаний" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "Відправити повідомленнÑ" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "Введіть повідомленнÑ" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "Поле повідомленнÑ" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "Введіть Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‚Ð° натиÑніть Enter, щоб надіÑлати" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "ПовідомленнÑ" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "Ðова беÑіда" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "Ðемає беÑід" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "Жодну беÑіду не обрано" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "Оберіть або розпочніть беÑіду" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "Тачпад.\n" "ПеретÑгніть на цю ділÑнку, щоб переміÑтити курÑор миші.\n" "Щоб переміÑтити курÑор натиÑніть та утримуйте.\n\n" "ПроÑте натиÑÐºÐ°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ відправлене на пов'Ñзаний приÑтрій.\n" "Ліва, ÑереднÑ, права кнопка та Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð»Ñ–Ñ‰Ð°Ñ‚ÐºÐ°." #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "Редагувати команду" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "Зберегти" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "Ðазва" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "Командний Ñ€Ñдок" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "Обрати виконуваний файл" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "Відкрити" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "ÐаÑтільний комп'ютер" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "Камера" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð±ÑƒÑ„ÐµÑ€Ñƒ обміну" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "Медіапрогравачі" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "Миша та клавіатура" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð³ÑƒÑ‡Ð½Ñ–Ñтю" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "Файли" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "Отримувати файли" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "Зберігати файли до" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "ÐÐ°Ð´Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "ÐкумулÑтор приÑтрою" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ низький рівень зарÑду" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ зарÑÐ´Ð¶Ð°Ð½Ð½Ñ Ð´Ð¾ вказаного рівнÑ" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ повний зарÑд" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "СиÑтемний акумулÑтор" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "ДілитиÑÑ ÑтатиÑтикою" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "ÐкумулÑтор" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "Команди" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "Додати команду" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "ДілитиÑÑ ÑповіщеннÑми" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "ДілитиÑÑ Ð¿Ñ€Ð¸ активноÑті" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "Додатки" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "СповіщеннÑ" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "Контакти" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "Вхідні дзвінки" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "ГучніÑть" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "Призупинити відтвореннÑ" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "Під Ñ‡Ð°Ñ Ð´Ð·Ð²Ñ–Ð½ÐºÑ–Ð²" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "Вимкнути мікрофон" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "ТелефоніÑ" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "Ð¡ÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð´Ñ–Ð¹" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "Скинути вÑі…" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "СкороченнÑ" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "Плагіни" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "ЕкÑпериментальне" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "Кеш приÑтрою" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "ОчиÑтити кеш…" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "ЗаÑтаріла підтримка SMS" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "ÐÐ²Ñ‚Ð¾Ð¼Ð¾Ð½Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "Додатково" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "Клавіатурні ÑкороченнÑ" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ñтрою" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "Пов'Ñзати" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "ПриÑтрій непов'Ñзаний" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "Ви можете налаштувати цей приÑтрій перед пов'ÑзуваннÑм" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "Дані щодо шифруваннÑ" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "Відв'Ñзати" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "До приÑтрою" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "Від приÑтрою" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "Ðічого" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "Відновити" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "Зменшити" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "Вимкнути" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "Ð’Ñтановити" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "ÐатиÑніть Esc щоб ÑкаÑувати або Backspace щоб Ñкинути комбінацію клавіш" #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "Ðазва приÑтрою" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "_Перейменувати" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "Оновити" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "Параметри мобільних приÑтроїв" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "СервіÑне меню" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "Меню приÑтрою" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "Редагувати назву приÑтрою" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "ПриÑтрої" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "Пошук приÑтроїв…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect залишаєтьÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¸Ð¼, коли GNOME Shell заблоковано" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "Браузерні розширеннÑ" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "Увімкнути" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "Цей приÑтрій Ñ” невидимим Ð´Ð»Ñ Ð½ÐµÐ¿Ð¾Ð²'Ñзаних приÑтроїв" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "ВидиміÑть вимкнено" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "Режим відображеннÑ" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "Панель" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "Меню кориÑтувача" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "Згенерувати журнал Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ñ€Ð¸Ð¼ÐºÐ¸" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "Про GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "Оберіть приÑтрій" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "Обрати" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "ПриÑтроїв не знайдено" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "Перелік приÑтроїв" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "Повідомити" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "ЩоÑÑŒ пішло не так" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect ÑтикнувÑÑ Ð· неочікуваною помилкою. Будь лаÑка, повідомте про проблему Ñ– додайте будь-Ñку інформацію, Ñка може допомогти." #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "Технічні подробиці" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "Відправити до мобільного приÑтрою" #: src/extension.js:52 msgid "Sync between your devices" msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð¼Ñ–Ð¶ приÑтроÑми" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d під'єднано" msgstr[1] "%d під'єднано" msgstr[2] "%d під'єднано" msgstr[3] "%d під'єднано" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "Редагувати" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "Видалити" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "Вимкнено" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Введіть нове ÑкороченнÑ, щоб замінити %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s вже викориÑтовуєтьÑÑ" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "Повна Ñ€ÐµÐ°Ð»Ñ–Ð·Ð°Ñ†Ñ–Ñ KDE Connect Ð´Ð»Ñ GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "kotyhoroshko" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ð»Ð°Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð·Ð°Ð¿Ð¸ÑуютьÑÑ. Виконайте вÑÑ– необхідні дії Ð´Ð»Ñ Ð²Ñ–Ð´Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸, а потім переглÑньте журнал." #: src/preferences/service.js:421 msgid "Review Log" msgstr "ПереглÑнути журнал" #: src/preferences/service.js:489 msgid "Laptop" msgstr "Ðоутбук" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "Смартфон" #: src/preferences/service.js:493 msgid "Tablet" msgstr "Планшет" #: src/preferences/service.js:495 msgid "Television" msgstr "Телевізор" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "Ðе пов'Ñзаний" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "Від'єднаний" #: src/preferences/service.js:525 msgid "Connected" msgstr "Під'єднаний" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñлужби…" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "ÐатиÑніть, щоб допомогти уÑунути проблему" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "ÐатиÑніть Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "Ðабрати номер" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "ПоділитиÑÑ Ñ„Ð°Ð¹Ð»Ð¾Ð¼" #: src/service/daemon.js:355 msgid "List available devices" msgstr "Перелічити доÑтупні приÑтрої" #: src/service/daemon.js:364 msgid "List all devices" msgstr "Перелічити вÑÑ– приÑтрої" #: src/service/daemon.js:373 msgid "Target Device" msgstr "Цільовий приÑтрій" #: src/service/daemon.js:415 msgid "Message Body" msgstr "Тіло повідомленнÑ" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "Відправити ÑповіщеннÑ" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "Ðазва заÑтоÑунку Ð´Ð»Ñ Ñповіщень" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "Тіло ÑповіщеннÑ" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "Піктограма ÑповіщеннÑ" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID ÑповіщеннÑ" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "Фото" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Пінг" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "Дзвеніти" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "ПоділитиÑÑ Ð¿Ð¾ÑиланнÑм" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "ПоділитиÑÑ Ñ‚ÐµÐºÑтом" #: src/service/daemon.js:532 msgid "Show release version" msgstr "Показати верÑÑ–ÑŽ програми" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-приÑтрій %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "Ключ перевірки: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "Запит на пов'ÑÐ·ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ñ–Ð´ %s" #: src/service/device.js:850 msgid "Reject" msgstr "Відхилити" #: src/service/device.js:855 msgid "Accept" msgstr "ПрийнÑти" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "ВидиміÑть було вимкнено через велику кількіÑть приÑтроїв у цій мережі." #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "OpenSSL не знайдено" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "Порт уже викориÑтовуєтьÑÑ" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "Обмін інформацією про акумулÑтор" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: ÐкумулÑтор зарÑджений" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "ПовніÑтю зарÑджений" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "%s: ÐкумулÑтор доÑÑг вказаного вами Ñ€Ñ–Ð²Ð½Ñ Ð·Ð°Ñ€Ñду" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "%d%% ЗарÑджено" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Ðизький зарÑд акумулÑтора" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "залишилоÑÑŒ %d%%" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "Буфер обміну" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "ÐадÑилати вміÑÑ‚ буфера обміну" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "Поміщати у буфер обміну" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "Отримувати з буферу обміну" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "ДоÑтуп до контактів повʼÑзаного приÑтрою" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "Знайти мій телефон" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "Дзвеніти повʼÑзаним приÑтроєм" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "Тачпад" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "ДозволÑÑ” пов'Ñзаному приÑтрою працювати віддаленою мишею та клавіатурою" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "Віддалене введеннÑ" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "ДвоÑтороннє віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñм медіа" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "Ðевідомо" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "ДілитиÑÑ ÑповіщеннÑми з повʼÑзаним приÑтроєм" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "СкаÑувати ÑповіщеннÑ" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "Закрити ÑповіщеннÑ" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "ВідповіÑти на ÑповіщеннÑ" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "Ðктивувати ÑповіщеннÑ" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "Запит пов'Ñзаного приÑтрою зробити фото та переÑлати його до цього ПК" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "Помилка передачі" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "Ðе вдалоÑÑ Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð¸Ñ‚Ð¸ «%s» до %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "ÐадÑÐ¸Ð»Ð°Ð½Ð½Ñ Ñ‚Ð° Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ñ–Ð½Ð³Ñƒ" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Пінг: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "ПрезентаціÑ" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "ВикориÑтовуйте Ñпарений приÑтрій презентатором" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "Виконати команди" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Виконуйте команди на вашому повʼÑзаному приÑтрої або дозвольте приÑтрою виконувати попередньо визначені команди на цьому ПК" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "ПереглÑд файлової ÑиÑтеми пов'Ñзаного приÑтрою" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "Змонтувати" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "Демонтувати" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s повідомлÑÑ” про помилку" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "ПоділитиÑÑŒ" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "ДілітьÑÑ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸ й URL-адреÑами між приÑтроÑми" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s не має дозволу вивантажувати файли" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "ÐŸÐµÑ€ÐµÐ´Ð°Ð²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Â«%s» від %s" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "Передача уÑпішна" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "Отримано «%s» від %s" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "Показати Ñ€Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "Відкрити файл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ «%s» від %s" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "ТекÑÑ‚ " #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "Ð’Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Â«%s» до %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "«%s» надіÑлано до %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "Відправити файли до %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "Відкрити піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "Відправити поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð¾ %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "ÐадÑилайте Ñ– читайте SMS вашого повʼÑзаного приÑтрою та отримуйте ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ нові SMS" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "Ðове SMS (URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "ВідповіÑти на SMS" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "ПоділитиÑÑŒ SMS" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "ГучніÑть ÑиÑтеми" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "Увімкніть ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ ÑиÑтемною гучніÑтю повʼÑзаного приÑтрою" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "PulseAudio не знайдено" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "Отримувати ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ виклики та змінювати ÑиÑтемну гучніÑть під Ñ‡Ð°Ñ Ð´Ð·Ð²Ñ–Ð½ÐºÐ°/поточних викликів" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Приглушити дзвінок" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "Ðевідомий контакт" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "Вхідний дзвінок" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "Поточний виклик" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "ФакÑ" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "Службовий" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "Мобільний" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "Домашній" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "Відправити до %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "Тільки що" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "Учора・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d хвилина" msgstr[1] "%d хвилини" msgstr[2] "%d хвилин" msgstr[3] "%d хвилини" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "недоÑтупний" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "Групове повідомленнÑ" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "Ви: %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "І ще %d інший контакт" msgstr[1] "І ще %d інші" msgstr[2] "І ще %d інших" msgstr[3] "І ще %d інших" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "Віддалена клавіатура на приÑтрої %s не активна" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (РозраховуєтьÑÑ…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d до зарÑдженнÑ)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d залишилоÑÑ)" #: src/shell/notification.js:58 msgid "Reply" msgstr "ВідповіÑти" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Поширюйте поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð·Ð° допомогою GSConnect, напрÑму до браузера або через SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "Ð¡ÐµÑ€Ð²Ñ–Ñ Ð½ÐµÐ´Ð¾Ñтупний" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "Відкрити у браузері" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/zh_CN.po000066400000000000000000001021651460766671100240300ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-06-10 16:49\n" "Last-Translator: \n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: zh-CN\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "KDE Connect çš„ GNOME 实现" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "GSConnect 团队" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "GSConnect 是 KDE Connect 的完整实现,特别适用于与 Nautilusã€Chrome å’Œ Firefox 集æˆçš„ GNOME Shell。 KDE Connect 团队拥有适用于 Linuxã€BSDã€Androidã€Sailfishã€iOSã€macOS å’Œ Windows 的应用程åºã€‚" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "您å¯ä»¥ä½¿ç”¨ GSConnect 安全地连接到移动设备和其他桌é¢ï¼Œæ¥ï¼š" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "å‘逿–‡ä»¶ã€é“¾æŽ¥å’Œæ–‡æœ¬" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "å‘é€ã€æŽ¥æ”¶æ¶ˆæ¯" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "åŒæ­¥å‰ªè´´æ¿å†…容" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "åŒæ­¥è”系人" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "åŒæ­¥é€šçŸ¥" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "控制媒体播放器" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "控制系统音é‡" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "执行预定义的命令" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "ä»¥åŠæ›´å¤šåŠŸèƒ½ã€‚" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "GNOME Shell 中的 GSConnect" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "连接到..." #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "å–æ¶ˆ" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "连接" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP 地å€" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "没有è”系人" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "帮助" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "输入电è¯å·ç æˆ–å§“å" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "å…¶ä»–" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "å‘é€çŸ­ä¿¡" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "å‘é€" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "设备已断开连接" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "å‘逿¶ˆæ¯" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "输入消æ¯" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "消æ¯å†…容" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "输入消æ¯å¹¶æŒ‰å›žè½¦å‘é€" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "消æ¯" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "新建对è¯" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "没有对è¯" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "未选择对è¯" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "选择或开始对è¯" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "触摸æ¿ã€‚\n" "在此区域上拖动以移动鼠标光标。\n" "é•¿æŒ‰æ‹–åŠ¨å¯æ‹–动鼠标光标。\n\n" "简å•的点击将被å‘é€åˆ°é…对设备。\n" "å·¦ã€ä¸­ã€å³æŒ‰é’®å’Œæ»šè½®æ»šåŠ¨ã€‚" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "编辑命令" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "ä¿å­˜" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "åç§°" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "命令行" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "é€‰æ‹©å¯æ‰§è¡Œæ–‡ä»¶" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "打开" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "桌é¢" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "相机" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "剪贴æ¿åŒæ­¥" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "媒体播放" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "鼠标和键盘" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "éŸ³é‡æŽ§åˆ¶" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "文件" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "文件接收" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "将文件ä¿å­˜åˆ°" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "共享" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "设备电é‡" #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "低电é‡é€šçŸ¥" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "充满电通知" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "系统电池" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "分享统计信æ¯" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "电池" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "命令" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "添加命令" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "åŒæ­¥é€šçŸ¥" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "设备亮起时ä»åŒæ­¥é€šçŸ¥" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "应用" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "通知" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "è”络" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "æ¥ç”µ" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "音é‡" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "æš‚åœåª’体" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "正在进行的呼å«" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "麦克风é™éŸ³" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "电è¯" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "æ“ä½œå¿«æ·æ–¹å¼" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "全部é‡ç½®..." #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "å¿«æ·æ–¹å¼" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "æ’ä»¶" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "实验功能" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "设备缓存" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "清除缓存..." #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "旧版短信支æŒ" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "自动挂载 SFTP" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "高级" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "键盘快æ·é”®" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "设备设置" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "é…对" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "设备未é…对" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "您å¯ä»¥åœ¨é…对å‰é…置此设备" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "加密信æ¯" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "å–æ¶ˆé…对" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "到设备" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "从设备" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "æ— " #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "æ¢å¤" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "é™ä½Ž" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "é™éŸ³" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "设置" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "按 Esc 键喿¶ˆï¼Œæˆ–按 Backsace é”®é‡ç½®é”®ç›˜å¿«æ·æ–¹å¼ã€‚" #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "设备åç§°" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "修改åç§°" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "刷新" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "设置" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "æœåŠ¡èœå•" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "设备èœå•" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "编辑设备åç§°" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "设备" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "正在æœç´¢è®¾å¤‡â€¦" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "扩展设置" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "当 GNOME Shell 被é”定时,GSConnect ä»ç„¶å¤„于活动状æ€" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "æµè§ˆå™¨é™„加组件" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "å¯ç”¨" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "此设备对未é…对的设备ä¸å¯è§" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "å‘现已ç¦ç”¨" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "显示模å¼" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "颿¿" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "用户èœå•" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "ç”Ÿæˆæ”¯æŒæ—¥å¿—" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "关于 GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "选择设备" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "选择" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "没有找到设备" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "设备列表" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "报告" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "出了一些错误" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "GSConnect é‡åˆ°äº†æ„外的错误,请报告这个错误,并在报告中包å«ä»»ä½•您认为å¯èƒ½æœ‰ç”¨çš„ä¿¡æ¯ã€‚" #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "技术信æ¯" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "å‘é€åˆ°ç§»åŠ¨è®¾å¤‡" #: src/extension.js:52 msgid "Sync between your devices" msgstr "åœ¨æ‚¨çš„è®¾å¤‡é—´åŒæ­¥" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d 连接" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "编辑" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "删除" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "ç¦ç”¨" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "è¾“å…¥æ–°çš„å¿«æ·æ–¹å¼ %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "%s 已在使用中" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "KDE Connect 在 GNOME 的完整实现" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "翻译贡献" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "正在记录调试日志。请采å–任何必è¦çš„æ­¥éª¤æ¥é‡çŽ°é—®é¢˜ï¼Œç„¶åŽå³å¯æŸ¥çœ‹æ—¥å¿—。" #: src/preferences/service.js:421 msgid "Review Log" msgstr "查看日志" #: src/preferences/service.js:489 msgid "Laptop" msgstr "笔记本电脑" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "智能手机" #: src/preferences/service.js:493 msgid "Tablet" msgstr "å¹³æ¿ç”µè„‘" #: src/preferences/service.js:495 msgid "Television" msgstr "电视" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "未é…对" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "已断开" #: src/preferences/service.js:525 msgid "Connected" msgstr "已连接" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "等待æœåŠ¡å“应..." #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "å•击以获å–疑难解答帮助" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "å•击以获å–详细信æ¯" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "拨打å·ç " #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "共享文件" #: src/service/daemon.js:355 msgid "List available devices" msgstr "å¯ç”¨è®¾å¤‡åˆ—表" #: src/service/daemon.js:364 msgid "List all devices" msgstr "所有å¯ç”¨è®¾å¤‡" #: src/service/daemon.js:373 msgid "Target Device" msgstr "目标设备" #: src/service/daemon.js:415 msgid "Message Body" msgstr "æ¶ˆæ¯æ­£æ–‡" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "å‘é€é€šçŸ¥" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "通知的应用åç§°" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "é€šçŸ¥çš„ä¿¡æ¯æ­£æ–‡" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "通知的图标" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "通知的ID" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "照片" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "å“铃" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "共享链接" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "共享文本" #: src/service/daemon.js:532 msgid "Show release version" msgstr "显示版本信æ¯" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "ä½äºŽ %s çš„è“牙设备" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "验è¯å¯†é’¥ï¼š %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "%s 请求é…对" #: src/service/device.js:850 msgid "Reject" msgstr "æ‹’ç»" #: src/service/device.js:855 msgid "Accept" msgstr "接å—" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "由于这个网络上的设备数é‡è¿‡å¤šï¼Œå‘现已被ç¦ç”¨ã€‚" #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "无法找到 OpenSSL(OpenSSL not found)" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "端å£å·²è¢«å ç”¨" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s: 电池已充满" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "充满电" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s: 电池电é‡ä½Ž" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "剩余 %d%%" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "剪贴æ¿" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "共享剪贴æ¿å†…容" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "推é€å‰ªè´´æ¿" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "获å–剪贴æ¿" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "查找我的手机" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "é¼ æ ‡" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "远程输入" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "未知" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "å–æ¶ˆé€šçŸ¥" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "关闭通知" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "回å¤é€šçŸ¥" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "激活通知" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "传输失败" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "无法将 “%s†å‘é€åˆ° %s" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "å‘é€å’ŒæŽ¥å— Ping" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "远程输入" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "è¿è¡Œå‘½ä»¤" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "æµè§ˆé…对设备的文件系统" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "挂载" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "å¸è½½" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "%s 报告了一个错误" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "共享" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "在设备间共享文件和 URL" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s ä¸å…许上传文件" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "文件传输中" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "正在从 %2$s 接收 “%1$sâ€" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "传输æˆåŠŸ" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "已从 %2$s 接收 “%1$sâ€" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "显示文件ä½ç½®" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "打开文件" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "无法从 %2$s 接收 “%1$sâ€" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "%s 共享的文本" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "正在将 “%s†å‘é€åˆ° %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "已将å‘é€ â€œ%s†到 %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "å‘逿–‡ä»¶åˆ° %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "完æˆåŽæ‰“å¼€" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "å‘é€é“¾æŽ¥åˆ° %s" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "短信" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "新短信(URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "回å¤çŸ­ä¿¡" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "共享短信" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "系统音é‡" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "未找到 PulseAudio" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "é™éŸ³é€šè¯" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "未知è”系人" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "æ¥ç”µ" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "呼å«ä¸­" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "传真" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "å•ä½" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "手机" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "ä½å®…" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "å‘é€åˆ° %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "刚æ‰" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "昨天・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d 分钟" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "无法使用的" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "群组消æ¯" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "ä½ : %s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "添加 %d ä½è”系人" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "%s 上的远程键盘未激活" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (估计...)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d∶%02d 为止)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (剩余 %d∶%02d)" #: src/shell/notification.js:58 msgid "Reply" msgstr "回å¤" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "通过 GSConnect 直接将链接å‘é€åˆ°æµè§ˆå™¨æˆ–短信。" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "æœåŠ¡ä¸å¯ç”¨" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "在æµè§ˆå™¨ä¸­æ‰“å¼€" GSConnect-gnome-shell-extension-gsconnect-43258f9/po/zh_TW.po000066400000000000000000000772751460766671100240770ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-04-29 00:46-0400\n" "PO-Revision-Date: 2023-05-01 15:40\n" "Last-Translator: \n" "Language-Team: Chinese Traditional\n" "Language: zh_TW\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-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: zh-TW\n" "X-Crowdin-File: /[GSConnect.gnome-shell-extension-gsconnect] main/po/org.gnome.Shell.Extensions.GSConnect.pot\n" "X-Crowdin-File-ID: 18\n" #. TRANSLATORS: Extension name #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:12 #: webextension/gettext.js:33 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:23 msgid "GSConnect Team" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:39 msgid "GSConnect is a complete implementation of KDE Connect especially for GNOME Shell with Nautilus, Chrome and Firefox integration. The KDE Connect team has applications for Linux, BSD, Android, Sailfish, iOS, macOS and Windows." msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:42 msgid "With GSConnect you can securely connect to mobile devices and other desktops to:" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:46 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:47 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:48 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Sync contacts" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync notifications" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Control system volume" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:130 msgid "GSConnect in GNOME Shell" msgstr "" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/connect-dialog.ui:20 data/ui/preferences-window.ui:821 msgid "Connect to…" msgstr "連線到…" #: data/ui/connect-dialog.ui:26 data/ui/legacy-messaging-dialog.ui:20 #: data/ui/legacy-messaging-dialog.ui:24 #: data/ui/notification-reply-dialog.ui:19 #: data/ui/notification-reply-dialog.ui:23 #: data/ui/preferences-command-editor.ui:21 #: data/ui/preferences-command-editor.ui:157 #: data/ui/preferences-shortcut-editor.ui:19 #: data/ui/service-device-chooser.ui:21 data/ui/service-device-chooser.ui:25 #: data/ui/service-error-dialog.ui:20 data/ui/service-error-dialog.ui:28 #: src/preferences/service.js:420 src/service/plugins/share.js:163 #: src/service/plugins/share.js:296 src/service/plugins/share.js:427 msgid "Cancel" msgstr "å–æ¶ˆ" #: data/ui/connect-dialog.ui:33 msgid "Connect" msgstr "連線" #: data/ui/connect-dialog.ui:80 msgid "IP Address" msgstr "IP ä½å€" #: data/ui/contact-chooser.ui:56 msgid "No contacts" msgstr "沒有è¯çµ¡äºº" #: data/ui/contact-chooser.ui:68 data/ui/messaging-window.ui:103 #: data/ui/mousepad-input-dialog.ui:56 data/ui/preferences-window.ui:847 msgid "Help" msgstr "幫助" #: data/ui/contact-chooser.ui:109 msgid "Type a phone number or name" msgstr "輸入電話號碼或姓å" #. TRANSLATORS: All other phone number types #: data/ui/contacts-address-row.ui:71 src/service/ui/contacts.js:149 msgid "Other" msgstr "" #. TRANSLATORS: Share URL by SMS #: data/ui/legacy-messaging-dialog.ui:15 src/service/daemon.js:292 #: src/service/daemon.js:406 src/service/plugins/sms.js:64 #: webextension/gettext.js:45 msgid "Send SMS" msgstr "發é€ç°¡è¨Š" #: data/ui/legacy-messaging-dialog.ui:32 data/ui/legacy-messaging-dialog.ui:36 #: data/ui/notification-reply-dialog.ui:31 #: data/ui/notification-reply-dialog.ui:35 src/service/plugins/share.js:428 msgid "Send" msgstr "傳é€" #: data/ui/legacy-messaging-dialog.ui:69 data/ui/messaging-window.ui:263 #: data/ui/notification-reply-dialog.ui:68 msgid "Device is disconnected" msgstr "設備已斷開" #: data/ui/messaging-conversation.ui:91 src/service/plugins/sms.js:56 msgid "Send Message" msgstr "傳é€è¨Šæ¯" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:73 msgid "Type a message" msgstr "輸入訊æ¯" #: data/ui/messaging-conversation.ui:99 msgid "Message Entry" msgstr "" #: data/ui/messaging-conversation.ui:100 msgid "Type a message and press Enter to send" msgstr "" #: data/ui/messaging-window.ui:21 src/service/plugins/sms.js:32 #: src/service/ui/messaging.js:1056 msgid "Messaging" msgstr "發é€ç°¡è¨Š" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1267 msgid "New Conversation" msgstr "æ–°çš„å°è©±" #: data/ui/messaging-window.ui:120 msgid "No Conversations" msgstr "沒有å°è©±" #: data/ui/messaging-window.ui:180 msgid "No conversation selected" msgstr "å°šç„¡å°è©±" #: data/ui/messaging-window.ui:196 msgid "Select or start a conversation" msgstr "鏿“‡æˆ–é–‹å§‹èŠå¤©" #: data/ui/mousepad-input-dialog.ui:97 msgid "Touchpad.\n" "Drag on this area to move mouse cursor.\n" "Press long to drag to drag mouse cursor.\n\n" "Simple click will be sent to paired device.\n" "Left, middle, right button, and wheel scrolls." msgstr "" #: data/ui/preferences-command-editor.ui:14 msgid "Edit Command" msgstr "" #: data/ui/preferences-command-editor.ui:28 msgid "Save" msgstr "儲存" #: data/ui/preferences-command-editor.ui:61 msgid "Name" msgstr "å稱" #: data/ui/preferences-command-editor.ui:99 msgid "Command Line" msgstr "命令列" #: data/ui/preferences-command-editor.ui:121 #: data/ui/preferences-command-editor.ui:150 msgid "Choose an executable" msgstr "鏿“‡å¯åŸ·è¡Œç¨‹å¼" #: data/ui/preferences-command-editor.ui:164 msgid "Open" msgstr "打開" #: data/ui/preferences-device-panel.ui:53 src/preferences/service.js:497 msgid "Desktop" msgstr "桌é¢" #: data/ui/preferences-device-panel.ui:102 msgid "Camera" msgstr "æ”影機" #: data/ui/preferences-device-panel.ui:159 msgid "Clipboard Sync" msgstr "åŒæ­¥å‰ªè²¼ç°¿" #: data/ui/preferences-device-panel.ui:225 msgid "Media Players" msgstr "媒體播放器" #: data/ui/preferences-device-panel.ui:282 msgid "Mouse & Keyboard" msgstr "滑鼠 & éµç›¤" #: data/ui/preferences-device-panel.ui:339 msgid "Volume Control" msgstr "éŸ³é‡æŽ§åˆ¶" #: data/ui/preferences-device-panel.ui:393 src/service/plugins/sftp.js:383 msgid "Files" msgstr "檔案" #: data/ui/preferences-device-panel.ui:445 msgid "Receive Files" msgstr "接收檔案" #: data/ui/preferences-device-panel.ui:504 msgid "Save files to" msgstr "" #: data/ui/preferences-device-panel.ui:565 #: data/ui/preferences-device-panel.ui:2227 msgid "Sharing" msgstr "分享" #: data/ui/preferences-device-panel.ui:596 msgid "Device Battery" msgstr "è£ç½®é›»æ± " #: data/ui/preferences-device-panel.ui:647 msgid "Low Battery Notification" msgstr "低電é‡é€šçŸ¥" #: data/ui/preferences-device-panel.ui:706 msgid "Charged Up to Custom Level Notification" msgstr "" #: data/ui/preferences-device-panel.ui:786 msgid "Fully Charged Notification" msgstr "電池充飽通知" #: data/ui/preferences-device-panel.ui:840 msgid "System Battery" msgstr "系統電池" #: data/ui/preferences-device-panel.ui:889 msgid "Share Statistics" msgstr "分享狀態" #: data/ui/preferences-device-panel.ui:943 #: data/ui/preferences-device-panel.ui:2273 src/service/plugins/battery.js:16 msgid "Battery" msgstr "電池" #: data/ui/preferences-device-panel.ui:973 #: data/ui/preferences-device-panel.ui:1058 #: data/ui/preferences-device-panel.ui:2319 #: src/service/plugins/runcommand.js:28 src/service/plugins/runcommand.js:36 #: src/service/plugins/runcommand.js:196 msgid "Commands" msgstr "指令" #: data/ui/preferences-device-panel.ui:1032 msgid "Add Command" msgstr "" #. TRANSLATORS: 'Share' is a verb here; this refers to the action of sharing #: data/ui/preferences-device-panel.ui:1120 msgid "Share Notifications" msgstr "分享通知" #: data/ui/preferences-device-panel.ui:1180 msgid "Share When Active" msgstr "" #: data/ui/preferences-device-panel.ui:1231 msgid "Applications" msgstr "應用程å¼" #: data/ui/preferences-device-panel.ui:1277 #: data/ui/preferences-device-panel.ui:2365 #: src/service/plugins/notification.js:19 msgid "Notifications" msgstr "通知" #: data/ui/preferences-device-panel.ui:1335 src/service/plugins/contacts.js:26 msgid "Contacts" msgstr "è¯ç¹«äºº" #: data/ui/preferences-device-panel.ui:1388 msgid "Incoming Calls" msgstr "來電" #: data/ui/preferences-device-panel.ui:1437 #: data/ui/preferences-device-panel.ui:1604 msgid "Volume" msgstr "音é‡" #: data/ui/preferences-device-panel.ui:1503 #: data/ui/preferences-device-panel.ui:1670 msgid "Pause Media" msgstr "æš«åœåª’é«”" #: data/ui/preferences-device-panel.ui:1556 msgid "Ongoing Calls" msgstr "通話中" #: data/ui/preferences-device-panel.ui:1726 msgid "Mute Microphone" msgstr "éœéŸ³éº¥å…‹é¢¨" #: data/ui/preferences-device-panel.ui:1780 #: data/ui/preferences-device-panel.ui:2411 src/service/plugins/telephony.js:17 msgid "Telephony" msgstr "電話" #: data/ui/preferences-device-panel.ui:1815 msgid "Action Shortcuts" msgstr "應用快æ·éµ" #: data/ui/preferences-device-panel.ui:1831 msgid "Reset All…" msgstr "全部é‡è¨­" #: data/ui/preferences-device-panel.ui:1883 msgid "Shortcuts" msgstr "å¿«æ·éµ" #: data/ui/preferences-device-panel.ui:1914 msgid "Plugins" msgstr "外掛" #: data/ui/preferences-device-panel.ui:1961 msgid "Experimental" msgstr "實驗性質" #: data/ui/preferences-device-panel.ui:2008 msgid "Device Cache" msgstr "" #: data/ui/preferences-device-panel.ui:2026 msgid "Clear Cache…" msgstr "" #: data/ui/preferences-device-panel.ui:2065 msgid "Legacy SMS Support" msgstr "舊版簡訊支æ´" #: data/ui/preferences-device-panel.ui:2122 msgid "SFTP Automount" msgstr "" #: data/ui/preferences-device-panel.ui:2177 #: data/ui/preferences-device-panel.ui:2503 msgid "Advanced" msgstr "進階" #: data/ui/preferences-device-panel.ui:2457 msgid "Keyboard Shortcuts" msgstr "éµç›¤å¿«æ·éµ" #: data/ui/preferences-device-panel.ui:2521 msgid "Device Settings" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/ui/preferences-device-panel.ui:2565 #: data/ui/preferences-device-panel.ui:2657 src/service/daemon.js:385 msgid "Pair" msgstr "é…å°" #: data/ui/preferences-device-panel.ui:2597 msgid "Device is unpaired" msgstr "è£ç½®å·²å–消é…å°" #: data/ui/preferences-device-panel.ui:2612 msgid "You may configure this device before pairing" msgstr "您å¯ä»¥åœ¨é…å°æ­¤è£ç½®å‰é€²è¡Œè¨­ç½®" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/ui/preferences-device-panel.ui:2652 src/preferences/device.js:391 msgid "Encryption Info" msgstr "加密資訊" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2663 src/service/daemon.js:394 msgid "Unpair" msgstr "å–æ¶ˆé…å°" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2675 msgid "To Device" msgstr "到è£ç½®" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2681 msgid "From Device" msgstr "從è£ç½®" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2693 #: data/ui/preferences-device-panel.ui:2726 msgid "Nothing" msgstr "無動作" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2700 #: data/ui/preferences-device-panel.ui:2733 msgid "Restore" msgstr "" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2707 #: data/ui/preferences-device-panel.ui:2740 msgid "Lower" msgstr "é™ä½ŽéŸ³é‡" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the actively ringing call #: data/ui/preferences-device-panel.ui:2714 #: data/ui/preferences-device-panel.ui:2747 #: src/service/plugins/telephony.js:199 msgid "Mute" msgstr "éœéŸ³" #: data/ui/preferences-shortcut-editor.ui:25 msgid "Set" msgstr "設定" #. Keys for cancelling (â›) or resetting (âˆ) a shortcut #: data/ui/preferences-shortcut-editor.ui:80 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "按下 Esc å–æ¶ˆå¿«æ·éµï¼Œæˆ–按下倒退éµï¼ˆBackspace)é‡è¨­å¿«æ·éµã€‚" #: data/ui/preferences-window.ui:24 msgid "Device Name" msgstr "è£ç½®å稱" #: data/ui/preferences-window.ui:61 msgid "_Rename" msgstr "釿–°å‘½å(_R)" #: data/ui/preferences-window.ui:98 data/ui/preferences-window.ui:112 msgid "Refresh" msgstr "釿–°æ•´ç†" #: data/ui/preferences-window.ui:139 src/extension.js:116 msgid "Mobile Settings" msgstr "手機設置" #: data/ui/preferences-window.ui:166 msgid "Service Menu" msgstr "" #: data/ui/preferences-window.ui:189 msgid "Device Menu" msgstr "" #: data/ui/preferences-window.ui:203 data/ui/preferences-window.ui:218 msgid "Edit Device Name" msgstr "修改è£ç½®å稱" #: data/ui/preferences-window.ui:278 msgid "Devices" msgstr "è£ç½®" #: data/ui/preferences-window.ui:328 src/preferences/service.js:659 msgid "Searching for devices…" msgstr "正在æœå°‹è£ç½®â€¦" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "" #: data/ui/preferences-window.ui:378 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "" #: data/ui/preferences-window.ui:410 msgid "Browser Add-Ons" msgstr "ç€è¦½å™¨å¤–掛" #: data/ui/preferences-window.ui:728 msgid "Enable" msgstr "啟用" #: data/ui/preferences-window.ui:760 msgid "This device is invisible to unpaired devices" msgstr "未é…å°è£ç½®çœ‹ä¸åˆ°æ­¤è£ç½®" #: data/ui/preferences-window.ui:772 src/service/manager.js:117 msgid "Discovery Disabled" msgstr "ç¦æ­¢è¢«æŽ¢ç´¢" #: data/ui/preferences-window.ui:826 msgid "Display Mode" msgstr "顯示模å¼" #. TRANSLATORS: Show device indicators in the top bar #: data/ui/preferences-window.ui:829 msgid "Panel" msgstr "頂端列" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/ui/preferences-window.ui:835 msgid "User Menu" msgstr "使用者é¸å–®" #. TRANSLATORS: Generate a support log #: data/ui/preferences-window.ui:843 src/preferences/service.js:417 msgid "Generate Support Log" msgstr "ç”¢ç”Ÿæ”¯æ´ Log" #: data/ui/preferences-window.ui:851 msgid "About GSConnect" msgstr "關於 GSConnect" #: data/ui/service-device-chooser.ui:15 msgid "Select a Device" msgstr "鏿“‡è£ç½®" #: data/ui/service-device-chooser.ui:33 data/ui/service-device-chooser.ui:38 msgid "Select" msgstr "鏿“‡" #. TRANSLATORS: No devices are known or available #: data/ui/service-device-chooser.ui:101 webextension/gettext.js:41 msgid "No Device Found" msgstr "找ä¸åˆ°è£ç½®" #: data/ui/service-device-chooser.ui:118 msgid "Device List" msgstr "" #: data/ui/service-error-dialog.ui:39 data/ui/service-error-dialog.ui:47 msgid "Report" msgstr "回報" #: data/ui/service-error-dialog.ui:79 msgid "Something’s gone wrong" msgstr "" #: data/ui/service-error-dialog.ui:91 msgid "GSConnect encountered an unexpected error. Please report the problem and include any information that may help." msgstr "" #: data/ui/service-error-dialog.ui:125 msgid "Technical Details" msgstr "" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:183 webextension/gettext.js:37 msgid "Send To Mobile Device" msgstr "傳é€åˆ°æ‰‹æ©Ÿ" #: src/extension.js:52 msgid "Sync between your devices" msgstr "" #: src/extension.js:161 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d å°è£ç½®å·²é€£ç·š" #: src/preferences/device.js:673 src/preferences/device.js:679 msgid "Edit" msgstr "編輯" #: src/preferences/device.js:688 src/preferences/device.js:694 msgid "Remove" msgstr "移除" #: src/preferences/device.js:948 src/preferences/device.js:976 msgid "Disabled" msgstr "åœç”¨" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:68 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "輸入新的快æ·éµå–代 %s" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/preferences/keybindings.js:134 #, javascript-format msgid "%s is already being used" msgstr "「%sã€å·²ç¶“被使用" #: src/preferences/service.js:376 msgid "A complete KDE Connect implementation for GNOME" msgstr "一個為 GNOME 完整實åšçš„ KDE Connect" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:385 msgid "translator-credits" msgstr "翻譯組員" #: src/preferences/service.js:418 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "除錯訊æ¯å·²è¢«ç´€éŒ„。請執行任何å¯èƒ½é€ æˆå•題的動作,然後查看日誌。" #: src/preferences/service.js:421 msgid "Review Log" msgstr "查看日誌" #: src/preferences/service.js:489 msgid "Laptop" msgstr "筆電" #: src/preferences/service.js:491 msgid "Smartphone" msgstr "智慧型手機" #: src/preferences/service.js:493 msgid "Tablet" msgstr "å¹³æ¿" #: src/preferences/service.js:495 msgid "Television" msgstr "電視" #: src/preferences/service.js:517 msgid "Unpaired" msgstr "未é…å°" #: src/preferences/service.js:521 msgid "Disconnected" msgstr "已中斷連線" #: src/preferences/service.js:525 msgid "Connected" msgstr "已連接" #: src/preferences/service.js:661 msgid "Waiting for service…" msgstr "等待伺æœå™¨â€¦" #: src/service/daemon.js:193 msgid "Click for help troubleshooting" msgstr "點擊以幫助除錯" #: src/service/daemon.js:204 msgid "Click for more information" msgstr "按一下以瞭解詳情" #: src/service/daemon.js:298 msgid "Dial Number" msgstr "撥打電話" #: src/service/daemon.js:304 src/service/daemon.js:502 #: src/service/plugins/share.js:33 msgid "Share File" msgstr "分享檔案" #: src/service/daemon.js:355 msgid "List available devices" msgstr "列出å¯ç”¨è£ç½®" #: src/service/daemon.js:364 msgid "List all devices" msgstr "列出所有è£ç½®" #: src/service/daemon.js:373 msgid "Target Device" msgstr "目標è£ç½®" #: src/service/daemon.js:415 msgid "Message Body" msgstr "訊æ¯å…§æ–‡" #: src/service/daemon.js:427 src/service/plugins/notification.js:58 msgid "Send Notification" msgstr "發é€é€šçŸ¥" #: src/service/daemon.js:436 msgid "Notification App Name" msgstr "程å¼å稱通知" #: src/service/daemon.js:445 msgid "Notification Body" msgstr "通知欄本身" #: src/service/daemon.js:454 msgid "Notification Icon" msgstr "通知圖示" #: src/service/daemon.js:463 msgid "Notification ID" msgstr "ID 通知" #: src/service/daemon.js:472 src/service/plugins/photo.js:16 #: src/service/plugins/photo.js:29 msgid "Photo" msgstr "æ‹æ”" #: src/service/daemon.js:481 src/service/plugins/ping.js:15 #: src/service/plugins/ping.js:22 src/service/plugins/ping.js:49 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:490 src/service/plugins/battery.js:248 #: src/service/plugins/battery.js:277 src/service/plugins/battery.js:306 #: src/service/plugins/findmyphone.js:24 msgid "Ring" msgstr "響音" #: src/service/daemon.js:511 src/service/plugins/share.js:49 #: src/service/ui/messaging.js:1250 src/service/ui/messaging.js:1258 msgid "Share Link" msgstr "共享éˆçµ" #: src/service/daemon.js:520 src/service/plugins/share.js:41 msgid "Share Text" msgstr "分享文字" #: src/service/daemon.js:532 msgid "Show release version" msgstr "顯示版本" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:173 #, javascript-format msgid "Bluetooth device at %s" msgstr "在「%sã€çš„è—牙è£ç½®" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:214 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:843 #, javascript-format msgid "Pair Request from %s" msgstr "「%sã€çš„é…å°è«‹æ±‚" #: src/service/device.js:850 msgid "Reject" msgstr "拒絕" #: src/service/device.js:855 msgid "Accept" msgstr "接å—" #: src/service/manager.js:118 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "因為此網域內的è£ç½®æ•¸é‡ï¼ŒæŽ¢ç´¢å·²åœç”¨" #: src/service/backends/lan.js:166 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:456 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:17 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:257 #, javascript-format msgid "%s: Battery is full" msgstr "%s:電池已充飽" #. TRANSLATORS: when the battery is fully charged #. TRANSLATORS: When the battery level is 100% #: src/service/plugins/battery.js:259 src/shell/device.js:122 msgid "Fully Charged" msgstr "充電完æˆ" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:286 #, javascript-format msgid "%s: Battery has reached custom charge level" msgstr "" #. TRANSLATORS: when the battery has reached custom charge level #: src/service/plugins/battery.js:288 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:315 #, javascript-format msgid "%s: Battery is low" msgstr "%s:電é‡éŽä½Ž" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:317 #, javascript-format msgid "%d%% remaining" msgstr "剩下 %d%%" #: src/service/plugins/clipboard.js:14 msgid "Clipboard" msgstr "剪貼簿" #: src/service/plugins/clipboard.js:15 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:27 msgid "Clipboard Push" msgstr "推é€å‰ªè²¼ç°¿" #: src/service/plugins/clipboard.js:35 msgid "Clipboard Pull" msgstr "接收剪貼簿" #: src/service/plugins/contacts.js:27 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:17 msgid "Find My Phone" msgstr "找尋我的手機" #: src/service/plugins/findmyphone.js:18 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:16 msgid "Mousepad" msgstr "觸控æ¿" #: src/service/plugins/mousepad.js:17 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:31 src/service/ui/mousepad.js:109 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:19 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:20 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:320 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:20 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:34 msgid "Cancel Notification" msgstr "å–æ¶ˆé€šçŸ¥" #: src/service/plugins/notification.js:42 msgid "Close Notification" msgstr "關閉通知" #: src/service/plugins/notification.js:50 msgid "Reply Notification" msgstr "回覆通知" #: src/service/plugins/notification.js:66 msgid "Activate Notification" msgstr "啟用通知" #: src/service/plugins/photo.js:17 msgid "Request the paired device to take a photo and transfer it to this PC" msgstr "" #: src/service/plugins/photo.js:224 src/service/plugins/share.js:132 #: src/service/plugins/share.js:208 src/service/plugins/share.js:319 msgid "Transfer Failed" msgstr "傳輸失敗" #. TRANSLATORS: eg. Failed to send "photo.jpg" to Google Pixel #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/photo.js:226 src/service/plugins/share.js:321 #, javascript-format msgid "Failed to send “%s†to %s" msgstr "傳é€ã€Œ%sã€åˆ°ã€Œ%sã€å¤±æ•—" #: src/service/plugins/ping.js:16 msgid "Send and receive pings" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:56 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:14 msgid "Presentation" msgstr "" #: src/service/plugins/presenter.js:15 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "Run Commands" msgstr "執行指令" #: src/service/plugins/runcommand.js:17 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:17 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:19 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:24 msgid "Mount" msgstr "掛載" #: src/service/plugins/sftp.js:32 msgid "Unmount" msgstr "å¸è¼‰" #: src/service/plugins/sftp.js:216 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:18 src/service/plugins/share.js:25 msgid "Share" msgstr "共享" #: src/service/plugins/share.js:20 msgid "Share files and URLs between devices" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:134 #, javascript-format msgid "%s is not allowed to upload files" msgstr "ä¸å…許「%sã€ä¸Šå‚³æª”案" #: src/service/plugins/share.js:156 src/service/plugins/share.js:289 msgid "Transferring File" msgstr "檔案傳é€ä¸­" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:158 #, javascript-format msgid "Receiving “%s†from %s" msgstr "正在接收「%sã€ï¼Œä¾†è‡ªæ–¼ã€Œ%sã€" #: src/service/plugins/share.js:177 src/service/plugins/share.js:309 msgid "Transfer Successful" msgstr "傳輸æˆåŠŸ" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:179 #, javascript-format msgid "Received “%s†from %s" msgstr "已接收「%sã€ï¼Œä¾†è‡ªæ–¼ã€Œ%sã€" #: src/service/plugins/share.js:189 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:194 msgid "Open File" msgstr "打開檔案" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:210 #, javascript-format msgid "Failed to receive “%s†from %s" msgstr "接收「%sã€å¤±æ•—,來自於「%sã€" #: src/service/plugins/share.js:241 #, javascript-format msgid "Text Shared By %s" msgstr "「%sã€åˆ†äº«çš„æ–‡å­—" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:291 #, javascript-format msgid "Sending “%s†to %s" msgstr "正在將「%sã€å‚³é€åˆ°ã€Œ%sã€" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:311 #, javascript-format msgid "Sent “%s†to %s" msgstr "「%sã€å·²å‚³é€åˆ°ã€Œ%sã€" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:379 #, javascript-format msgid "Send files to %s" msgstr "將檔案傳é€åˆ°ã€Œ%sã€" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:383 msgid "Open when done" msgstr "ç•¶å®Œæˆæ™‚開啟" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:422 #, javascript-format msgid "Send a link to %s" msgstr "發é€ä¸€å€‹éˆçµåˆ°ã€Œ%sã€" #: src/service/plugins/sms.js:18 msgid "SMS" msgstr "簡訊" #: src/service/plugins/sms.js:19 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:40 msgid "New SMS (URI)" msgstr "新增簡訊(URI)" #: src/service/plugins/sms.js:48 msgid "Reply SMS" msgstr "回覆簡訊" #: src/service/plugins/sms.js:72 msgid "Share SMS" msgstr "分享簡訊" #: src/service/plugins/systemvolume.js:15 msgid "System Volume" msgstr "系統音é‡" #: src/service/plugins/systemvolume.js:16 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:60 msgid "PulseAudio not found" msgstr "" #: src/service/plugins/telephony.js:18 msgid "Be notified about calls and adjust system volume during ringing/ongoing calls" msgstr "" #. TRANSLATORS: Silence the actively ringing call #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "通話éœéŸ³" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:157 src/service/plugins/telephony.js:176 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:749 msgid "Unknown Contact" msgstr "未知的è¯çµ¡äºº" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:195 msgid "Incoming call" msgstr "來電" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:210 msgid "Ongoing call" msgstr "通話中" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:134 msgid "Fax" msgstr "" #. TRANSLATORS: A work or office phone number #: src/service/ui/contacts.js:138 msgid "Work" msgstr "" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:142 msgid "Mobile" msgstr "" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:146 msgid "Home" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #. Update UI #: src/service/ui/contacts.js:509 src/service/ui/contacts.js:524 #, javascript-format msgid "Send to %s" msgstr "傳é€åˆ°ã€Œ%sã€" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:104 src/service/ui/messaging.js:145 msgid "Just now" msgstr "就在剛æ‰" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:113 #, javascript-format msgid "Yesterday・%s" msgstr "昨天・%s" #: src/service/ui/messaging.js:150 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d 分é˜" #: src/service/ui/messaging.js:400 msgid "Not available" msgstr "無法使用" #: src/service/ui/messaging.js:757 msgid "Group Message" msgstr "群組簡訊" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:772 #, javascript-format msgid "You: %s" msgstr "你:%s" #: src/service/ui/messaging.js:958 #, javascript-format msgid "And %d other contact" msgid_plural "And %d others" msgstr[0] "å’Œ %d ä½å…¶ä»–è¯çµ¡äºº" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:117 #, javascript-format msgid "Remote keyboard on %s is not active" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:127 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (計算中…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:136 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%%(è·é›¢å……滿還需 %d∶%02d )" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:144 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d 剩餘)" #: src/shell/notification.js:58 msgid "Reply" msgstr "回覆" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:35 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "直接用ç€è¦½å™¨æˆ–經由簡訊和 GSConnect 共享éˆçµ" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:39 msgid "Service Unavailable" msgstr "æœå‹™ç„¡æ³•使用" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:43 msgid "Open in Browser" msgstr "以ç€è¦½å™¨é–‹å•Ÿ" GSConnect-gnome-shell-extension-gsconnect-43258f9/pyproject.toml000066400000000000000000000003321460766671100247560ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later [tool.black] line-length = 80 target-version = ['py36'] include = 'nautilus-extension/.*\.py$' GSConnect-gnome-shell-extension-gsconnect-43258f9/src/000077500000000000000000000000001460766671100226335ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/src/extension.js000066400000000000000000000322761460766671100252170ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import * as QuickSettings from 'resource:///org/gnome/shell/ui/quickSettings.js'; // Bootstrap import { Extension, gettext as _, ngettext } from 'resource:///org/gnome/shell/extensions/extension.js'; import Config from './config.js'; import * as Clipboard from './shell/clipboard.js'; import * as Device from './shell/device.js'; import * as Keybindings from './shell/keybindings.js'; import * as Notification from './shell/notification.js'; import * as Input from './shell/input.js'; import * as Utils from './shell/utils.js'; import * as Remote from './utils/remote.js'; import setup from './utils/setup.js'; const QuickSettingsMenu = Main.panel.statusArea.quickSettings; /** * A System Indicator used as the hub for spawning device indicators and * indicating that the extension is active when there are none. */ const ServiceToggle = GObject.registerClass({ GTypeName: 'GSConnectServiceIndicator', }, class ServiceToggle extends QuickSettings.QuickMenuToggle { _init() { super._init({ title: 'GSConnect', toggleMode: true, }); this.set({iconName: 'org.gnome.Shell.Extensions.GSConnect-symbolic'}); // Set QuickMenuToggle header. this.menu.setHeader('org.gnome.Shell.Extensions.GSConnect-symbolic', 'GSConnect', _('Sync between your devices')); this._menus = {}; this._keybindings = new Keybindings.Manager(); // GSettings this.settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect', null ), path: '/org/gnome/shell/extensions/gsconnect/', }); // Bind the toggle to enabled key this.settings.bind('enabled', this, 'checked', Gio.SettingsBindFlags.DEFAULT); this._enabledId = this.settings.connect( 'changed::enabled', this._onEnabledChanged.bind(this) ); this._panelModeId = this.settings.connect( 'changed::show-indicators', this._sync.bind(this) ); // Service Proxy this.service = new Remote.Service(); this._deviceAddedId = this.service.connect( 'device-added', this._onDeviceAdded.bind(this) ); this._deviceRemovedId = this.service.connect( 'device-removed', this._onDeviceRemoved.bind(this) ); this._serviceChangedId = this.service.connect( 'notify::active', this._onServiceChanged.bind(this) ); // Service Menu -> Devices Section this.deviceSection = new PopupMenu.PopupMenuSection(); this.deviceSection.actor.add_style_class_name('gsconnect-device-section'); this.settings.bind( 'show-indicators', this.deviceSection.actor, 'visible', Gio.SettingsBindFlags.INVERT_BOOLEAN ); this.menu.addMenuItem(this.deviceSection); // Service Menu -> Separator this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); // Service Menu -> "Mobile Settings" this.menu.addSettingsAction( _('Mobile Settings'), 'org.gnome.Shell.Extensions.GSConnect.Preferences.desktop'); // Prime the service this._initService(); } async _initService() { try { if (this.settings.get_boolean('enabled')) await this.service.start(); else await this.service.reload(); } catch (e) { logError(e, 'GSConnect'); } } _sync() { const available = this.service.devices.filter(device => { return (device.connected && device.paired); }); const panelMode = this.settings.get_boolean('show-indicators'); // Hide status indicator if in Panel mode or no devices are available serviceIndicator._indicator.visible = (!panelMode && available.length); // Show device indicators in Panel mode if available for (const device of this.service.devices) { const isAvailable = available.includes(device); const indicator = Main.panel.statusArea[device.g_object_path]; indicator.visible = panelMode && isAvailable; const menu = this._menus[device.g_object_path]; menu.actor.visible = !panelMode && isAvailable; menu._title.actor.visible = !panelMode && isAvailable; } // Set subtitle on Quick Settings tile if (available.length === 1) { this.subtitle = available[0].name; } else if (available.length > 1) { // TRANSLATORS: %d is the number of devices connected this.subtitle = ngettext( '%d Connected', '%d Connected', available.length ).format(available.length); } else { this.subtitle = null; } } _onDeviceChanged(device, changed, invalidated) { try { const properties = changed.deepUnpack(); if (properties.hasOwnProperty('Connected') || properties.hasOwnProperty('Paired')) this._sync(); } catch (e) { logError(e, 'GSConnect'); } } _onDeviceAdded(service, device) { try { // Device Indicator const indicator = new Device.Indicator({device: device}); Main.panel.addToStatusArea(device.g_object_path, indicator); // Device Menu const menu = new Device.Menu({ device: device, menu_type: 'list', }); this._menus[device.g_object_path] = menu; this.deviceSection.addMenuItem(menu); // Device Settings device.settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect.Device', true ), path: `/org/gnome/shell/extensions/gsconnect/device/${device.id}/`, }); // Keyboard Shortcuts device.__keybindingsChangedId = device.settings.connect( 'changed::keybindings', this._onDeviceKeybindingsChanged.bind(this, device) ); this._onDeviceKeybindingsChanged(device); // Watch the for status changes device.__deviceChangedId = device.connect( 'g-properties-changed', this._onDeviceChanged.bind(this) ); this._sync(); } catch (e) { logError(e, 'GSConnect'); } } _onDeviceRemoved(service, device, sync = true) { try { // Stop watching for status changes if (device.__deviceChangedId) device.disconnect(device.__deviceChangedId); // Release keybindings if (device.__keybindingsChangedId) { device.settings.disconnect(device.__keybindingsChangedId); device._keybindings.map(id => this._keybindings.remove(id)); } // Destroy the indicator Main.panel.statusArea[device.g_object_path].destroy(); // Destroy the menu this._menus[device.g_object_path].destroy(); delete this._menus[device.g_object_path]; if (sync) this._sync(); } catch (e) { logError(e, 'GSConnect'); } } _onDeviceKeybindingsChanged(device) { try { // Reset any existing keybindings if (device.hasOwnProperty('_keybindings')) device._keybindings.map(id => this._keybindings.remove(id)); device._keybindings = []; // Get the keybindings const keybindings = device.settings.get_value('keybindings').deepUnpack(); // Apply the keybindings for (const [action, accelerator] of Object.entries(keybindings)) { const [, name, parameter] = Gio.Action.parse_detailed_name(action); const actionId = this._keybindings.add( accelerator, () => device.action_group.activate_action(name, parameter) ); if (actionId !== 0) device._keybindings.push(actionId); } } catch (e) { logError(e, 'GSConnect'); } } async _onEnabledChanged(settings, key) { try { if (this.settings.get_boolean('enabled')) await this.service.start(); else await this.service.stop(); } catch (e) { logError(e, 'GSConnect'); } } async _onServiceChanged(service, pspec) { try { // If it's enabled, we should try to restart now if (this.settings.get_boolean('enabled')) await this.service.start(); } catch (e) { logError(e, 'GSConnect'); } } destroy() { // Unhook from Remote.Service if (this.service) { this.service.disconnect(this._serviceChangedId); this.service.disconnect(this._deviceAddedId); this.service.disconnect(this._deviceRemovedId); for (const device of this.service.devices) this._onDeviceRemoved(this.service, device, false); if (!this.settings.get_boolean('keep-alive-when-locked')) this.service.stop(); this.service.destroy(); } // Disconnect any keybindings this._keybindings.destroy(); // Disconnect from any GSettings changes this.settings.disconnect(this._enabledId); this.settings.disconnect(this._panelModeId); this.settings.run_dispose(); // Destroy the PanelMenu.SystemIndicator actors this.menu.destroy(); super.destroy(); } }); const ServiceIndicator = GObject.registerClass( class ServiceIndicator extends QuickSettings.SystemIndicator { _init() { super._init(); // Create the icon for the indicator this._indicator = this._addIndicator(); this._indicator.icon_name = 'org.gnome.Shell.Extensions.GSConnect-symbolic'; // Hide the indicator by default this._indicator.visible = false; // Create the toggle menu and associate it with the indicator this.quickSettingsItems.push(new ServiceToggle()); // Add the indicator to the panel and the toggle to the menu QuickSettingsMenu.addExternalIndicator(this); } destroy() { // Set enabled state to false to kill the service on destroy this.quickSettingsItems.forEach(item => item.destroy()); // Destroy the indicator this._indicator.destroy(); super.destroy(); } }); let serviceIndicator = null; export default class GSConnectExtension extends Extension { lockscreenInput = null; constructor(metadata) { super(metadata); setup(this.path); // If installed as a user extension, this checks the permissions // on certain critical files in the extension directory // to ensure that they have the executable bit set, // and makes them executable if not. Some packaging methods // (particularly GitHub Actions artifacts) automatically remove // executable bits from all contents, presumably for security. Utils.ensurePermissions(); // If installed as a user extension, this will install the Desktop entry, // DBus and systemd service files necessary for DBus activation and // GNotifications. Since there's no uninit()/uninstall() hook for extensions // and they're only used *by* GSConnect, they should be okay to leave. Utils.installService(); // These modify the notification source for GSConnect's GNotifications and // need to be active even when the extension is disabled (eg. lock screen). // Since they *only* affect notifications from GSConnect, it should be okay // to leave them applied. Notification.patchGSConnectNotificationSource(); Notification.patchGtkNotificationDaemon(); // This watches for the service to start and exports a custom clipboard // portal for use on Wayland Clipboard.watchService(); } enable() { serviceIndicator = new ServiceIndicator(); Notification.patchGtkNotificationSources(); this.lockscreenInput = new Input.LockscreenRemoteAccess(); this.lockscreenInput.patchInhibitor(); } disable() { serviceIndicator.destroy(); serviceIndicator = null; Notification.unpatchGtkNotificationSources(); if (this.lockscreenInput) { this.lockscreenInput.unpatchInhibitor(); this.lockscreenInput = null; } } } GSConnect-gnome-shell-extension-gsconnect-43258f9/src/gsconnect-preferences000077500000000000000000000056131460766671100270500ustar00rootroot00000000000000#!/usr/bin/env -S gjs -m // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later // -*- mode: js; -*- import Gdk from 'gi://Gdk?version=3.0'; import 'gi://GdkPixbuf?version=2.0'; import Gio from 'gi://Gio?version=2.0'; import GLib from 'gi://GLib?version=2.0'; import GObject from 'gi://GObject?version=2.0'; import Gtk from 'gi://Gtk?version=3.0'; import system from 'system'; import './preferences/init.js'; import {Window} from './preferences/service.js'; import Config from './config.js'; import('gi://GioUnix?version=2.0').catch(() => {}); // Set version for optional dependency /** * Class representing the GSConnect service daemon. */ const Preferences = GObject.registerClass({ GTypeName: 'GSConnectPreferences', Implements: [Gio.ActionGroup], }, class Preferences extends Gtk.Application { _init() { super._init({ application_id: 'org.gnome.Shell.Extensions.GSConnect.Preferences', resource_base_path: '/org/gnome/Shell/Extensions/GSConnect', }); GLib.set_prgname('gsconnect-preferences'); GLib.set_application_name(_('GSConnect Preferences')); } vfunc_activate() { if (this._window === undefined) { this._window = new Window({ application: this, }); } this._window.present(); } vfunc_startup() { super.vfunc_startup(); // Init some resources const provider = new Gtk.CssProvider(); provider.load_from_resource(`${Config.APP_PATH}/application.css`); Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ); const actions = [ ['refresh', null], ['connect', GLib.VariantType.new('s')], ]; for (const [name, type] of actions) { const action = new Gio.SimpleAction({ name: name, parameter_type: type, }); this.add_action(action); } } vfunc_activate_action(action_name, parameter) { try { const paramArray = []; if (parameter instanceof GLib.Variant) paramArray[0] = parameter; this.get_dbus_connection().call( 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', 'org.freedesktop.Application', 'ActivateAction', GLib.Variant.new('(sava{sv})', [action_name, paramArray, {}]), null, Gio.DBusCallFlags.NONE, -1, null, null ); } catch (e) { logError(e); } } }); await (new Preferences()).runAsync([system.programInvocationName].concat(ARGV)); GSConnect-gnome-shell-extension-gsconnect-43258f9/src/preferences/000077500000000000000000000000001460766671100251345ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/src/preferences/device.js000066400000000000000000001040311460766671100267300ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import Pango from 'gi://Pango'; import Config from '../config.js'; import plugins from '../service/plugins/index.js'; import * as Keybindings from './keybindings.js'; // Build a list of plugins and shortcuts for devices const DEVICE_PLUGINS = []; const DEVICE_SHORTCUTS = {}; for (const name in plugins) { const module = plugins[name]; if (module.Metadata === undefined) continue; // Plugins DEVICE_PLUGINS.push(name); // Shortcuts (GActions without parameters) for (const [name, action] of Object.entries(module.Metadata.actions)) { if (action.parameter_type === null) DEVICE_SHORTCUTS[name] = [action.icon_name, action.label]; } } /** * A Gtk.ListBoxHeaderFunc for sections that adds separators between each row. * * @param {Gtk.ListBoxRow} row - The current row * @param {Gtk.ListBoxRow} before - The previous row */ export function rowSeparators(row, before) { const header = row.get_header(); if (before === null) { if (header !== null) header.destroy(); return; } if (header === null) row.set_header(new Gtk.Separator({visible: true})); } /** * A Gtk.ListBoxSortFunc for SectionRow rows * * @param {Gtk.ListBoxRow} row1 - The first row * @param {Gtk.ListBoxRow} row2 - The second row * @return {number} -1, 0 or 1 */ export function titleSortFunc(row1, row2) { if (!row1.title || !row2.title) return 0; return row1.title.localeCompare(row2.title); } /** * A row for a section of settings */ const SectionRow = GObject.registerClass({ GTypeName: 'GSConnectPreferencesSectionRow', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/preferences-section-row.ui', Children: ['icon-image', 'title-label', 'subtitle-label'], Properties: { 'gicon': GObject.ParamSpec.object( 'gicon', 'GIcon', 'A GIcon for the row', GObject.ParamFlags.READWRITE, Gio.Icon.$gtype ), 'icon-name': GObject.ParamSpec.string( 'icon-name', 'Icon Name', 'An icon name for the row', GObject.ParamFlags.READWRITE, null ), 'subtitle': GObject.ParamSpec.string( 'subtitle', 'Subtitle', 'A subtitle for the row', GObject.ParamFlags.READWRITE, null ), 'title': GObject.ParamSpec.string( 'title', 'Title', 'A title for the row', GObject.ParamFlags.READWRITE, null ), 'widget': GObject.ParamSpec.object( 'widget', 'Widget', 'An action widget for the row', GObject.ParamFlags.READWRITE, Gtk.Widget.$gtype ), }, }, class SectionRow extends Gtk.ListBoxRow { _init(params = {}) { super._init(); // NOTE: we can't pass construct properties to _init() because the // template children are not assigned until after it runs. this.freeze_notify(); Object.assign(this, params); this.thaw_notify(); } get icon_name() { return this.icon_image.icon_name; } set icon_name(icon_name) { if (this.icon_name === icon_name) return; this.icon_image.visible = !!icon_name; this.icon_image.icon_name = icon_name; this.notify('icon-name'); } get gicon() { return this.icon_image.gicon; } set gicon(gicon) { if (this.gicon === gicon) return; this.icon_image.visible = !!gicon; this.icon_image.gicon = gicon; this.notify('gicon'); } get title() { return this.title_label.label; } set title(text) { if (this.title === text) return; this.title_label.visible = !!text; this.title_label.label = text; this.notify('title'); } get subtitle() { return this.subtitle_label.label; } set subtitle(text) { if (this.subtitle === text) return; this.subtitle_label.visible = !!text; this.subtitle_label.label = text; this.notify('subtitle'); } get widget() { if (this._widget === undefined) this._widget = null; return this._widget; } set widget(widget) { if (this.widget === widget) return; if (this.widget instanceof Gtk.Widget) this.widget.destroy(); // Add the widget this._widget = widget; this.get_child().attach(widget, 2, 0, 1, 2); this.notify('widget'); } }); /** * Command Editor Dialog */ const CommandEditor = GObject.registerClass({ GTypeName: 'GSConnectPreferencesCommandEditor', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/preferences-command-editor.ui', Children: [ 'cancel-button', 'save-button', 'command-entry', 'name-entry', 'command-chooser', ], }, class CommandEditor extends Gtk.Dialog { _onBrowseCommand(entry, icon_pos, event) { this.command_chooser.present(); } _onCommandChosen(dialog, response_id) { if (response_id === Gtk.ResponseType.OK) this.command_entry.text = dialog.get_filename(); dialog.hide(); } _onEntryChanged(entry, pspec) { this.save_button.sensitive = (this.command_name && this.command_line); } get command_line() { return this.command_entry.text; } set command_line(text) { this.command_entry.text = text; } get command_name() { return this.name_entry.text; } set command_name(text) { this.name_entry.text = text; } }); /** * A widget for configuring a remote device. */ export const Panel = GObject.registerClass({ GTypeName: 'GSConnectPreferencesDevicePanel', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device being configured', GObject.ParamFlags.READWRITE, GObject.Object.$gtype ), }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/preferences-device-panel.ui', Children: [ 'sidebar', 'stack', 'infobar', // Sharing 'sharing', 'sharing-page', 'desktop-list', 'clipboard', 'clipboard-sync', 'mousepad', 'mpris', 'systemvolume', 'share', 'share-list', 'receive-files', 'receive-directory', // Battery 'battery', 'battery-device-label', 'battery-device', 'battery-device-list', 'battery-system-label', 'battery-system', 'battery-system-list', 'battery-custom-notification-value', // RunCommand 'runcommand', 'runcommand-page', 'command-list', 'command-add', // Notifications 'notification', 'notification-page', 'notification-list', 'notification-apps', // Telephony 'telephony', 'telephony-page', 'ringing-list', 'ringing-volume', 'talking-list', 'talking-volume', // Shortcuts 'shortcuts-page', 'shortcuts-actions', 'shortcuts-actions-title', 'shortcuts-actions-list', // Advanced 'advanced-page', 'plugin-list', 'experimental-list', 'device-menu', ], }, class Panel extends Gtk.Grid { _init(device) { super._init({ device: device, }); // GSettings this.settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect.Device', true ), path: `/org/gnome/shell/extensions/gsconnect/device/${device.id}/`, }); // Infobar this.device.bind_property( 'paired', this.infobar, 'reveal-child', (GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN) ); this._setupActions(); // Settings Pages this._sharingSettings(); this._batterySettings(); this._runcommandSettings(); this._notificationSettings(); this._telephonySettings(); // -------------------------- this._keybindingSettings(); this._advancedSettings(); // Separate plugins and other settings this.sidebar.set_header_func((row, before) => { if (row.get_name() === 'shortcuts') row.set_header(new Gtk.Separator({visible: true})); }); } get menu() { if (this._menu === undefined) { this._menu = this.device_menu; this._menu.prepend_section(null, this.device.menu); this.insert_action_group('device', this.device.action_group); } return this._menu; } get_incoming_supported(type) { const incoming = this.settings.get_strv('incoming-capabilities'); return incoming.includes(`kdeconnect.${type}`); } get_outgoing_supported(type) { const outgoing = this.settings.get_strv('outgoing-capabilities'); return outgoing.includes(`kdeconnect.${type}`); } _onKeynavFailed(widget, direction) { if (direction === Gtk.DirectionType.UP && widget.prev) widget.prev.child_focus(direction); else if (direction === Gtk.DirectionType.DOWN && widget.next) widget.next.child_focus(direction); return true; } _onSwitcherRowSelected(box, row) { this.stack.set_visible_child_name(row.get_name()); } _onSectionRowActivated(box, row) { if (row.widget !== undefined) row.widget.active = !row.widget.active; } _onToggleRowActivated(box, row) { const widget = row.get_child().get_child_at(1, 0); widget.active = !widget.active; } _onEncryptionInfo() { const dialog = new Gtk.MessageDialog({ buttons: Gtk.ButtonsType.OK, text: _('Encryption Info'), secondary_text: this.device.encryption_info, modal: true, transient_for: this.get_toplevel(), }); dialog.connect('response', (dialog) => dialog.destroy()); dialog.present(); } _deviceAction(action, parameter) { this.action_group.activate_action(action.name, parameter); } dispose() { if (this._commandEditor !== undefined) this._commandEditor.destroy(); // Device signals this.device.action_group.disconnect(this._actionAddedId); this.device.action_group.disconnect(this._actionRemovedId); // GSettings for (const settings of Object.values(this._pluginSettings)) settings.run_dispose(); this.settings.disconnect(this._keybindingsId); this.settings.disconnect(this._disabledPluginsId); this.settings.disconnect(this._supportedPluginsId); this.settings.run_dispose(); } pluginSettings(name) { if (this._pluginSettings === undefined) this._pluginSettings = {}; if (!this._pluginSettings.hasOwnProperty(name)) { const meta = plugins[name].Metadata; this._pluginSettings[name] = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup(meta.id, -1), path: `${this.settings.path}plugin/${name}/`, }); } return this._pluginSettings[name]; } _setupActions() { this.actions = new Gio.SimpleActionGroup(); this.insert_action_group('settings', this.actions); let settings = this.pluginSettings('battery'); this.actions.add_action(settings.create_action('send-statistics')); this.actions.add_action(settings.create_action('low-battery-notification')); this.actions.add_action(settings.create_action('custom-battery-notification')); this.actions.add_action(settings.create_action('custom-battery-notification-value')); this.actions.add_action(settings.create_action('full-battery-notification')); settings = this.pluginSettings('clipboard'); this.actions.add_action(settings.create_action('send-content')); this.actions.add_action(settings.create_action('receive-content')); settings = this.pluginSettings('contacts'); this.actions.add_action(settings.create_action('contacts-source')); settings = this.pluginSettings('mousepad'); this.actions.add_action(settings.create_action('share-control')); settings = this.pluginSettings('mpris'); this.actions.add_action(settings.create_action('share-players')); settings = this.pluginSettings('notification'); this.actions.add_action(settings.create_action('send-notifications')); this.actions.add_action(settings.create_action('send-active')); settings = this.pluginSettings('sftp'); this.actions.add_action(settings.create_action('automount')); settings = this.pluginSettings('share'); this.actions.add_action(settings.create_action('receive-files')); settings = this.pluginSettings('sms'); this.actions.add_action(settings.create_action('legacy-sms')); settings = this.pluginSettings('systemvolume'); this.actions.add_action(settings.create_action('share-sinks')); settings = this.pluginSettings('telephony'); this.actions.add_action(settings.create_action('ringing-volume')); this.actions.add_action(settings.create_action('ringing-pause')); this.actions.add_action(settings.create_action('talking-volume')); this.actions.add_action(settings.create_action('talking-pause')); this.actions.add_action(settings.create_action('talking-microphone')); // Pair Actions const encryption_info = new Gio.SimpleAction({name: 'encryption-info'}); encryption_info.connect('activate', this._onEncryptionInfo.bind(this)); this.actions.add_action(encryption_info); const status_pair = new Gio.SimpleAction({name: 'pair'}); status_pair.connect('activate', this._deviceAction.bind(this.device)); this.settings.bind('paired', status_pair, 'enabled', 16); this.actions.add_action(status_pair); const status_unpair = new Gio.SimpleAction({name: 'unpair'}); status_unpair.connect('activate', this._deviceAction.bind(this.device)); this.settings.bind('paired', status_unpair, 'enabled', 0); this.actions.add_action(status_unpair); } /** * Sharing Settings */ _sharingSettings() { // Share Plugin const settings = this.pluginSettings('share'); settings.connect( 'changed::receive-directory', this._onReceiveDirectoryChanged.bind(this) ); this._onReceiveDirectoryChanged(settings, 'receive-directory'); // Visibility this.desktop_list.foreach(row => { const name = row.get_name(); row.visible = this.get_outgoing_supported(`${name}.request`); }); // Separators & Sorting this.desktop_list.set_header_func(rowSeparators); this.desktop_list.set_sort_func((row1, row2) => { row1 = row1.get_child().get_child_at(0, 0); row2 = row2.get_child().get_child_at(0, 0); return row1.label.localeCompare(row2.label); }); this.share_list.set_header_func(rowSeparators); // Scroll with keyboard focus const sharing_box = this.sharing_page.get_child().get_child(); sharing_box.set_focus_vadjustment(this.sharing_page.vadjustment); // Continue focus chain between lists this.desktop_list.next = this.share_list; this.share_list.prev = this.desktop_list; } _onReceiveDirectoryChanged(settings, key) { let receiveDir = settings.get_string(key); if (receiveDir.length === 0) { receiveDir = GLib.get_user_special_dir( GLib.UserDirectory.DIRECTORY_DOWNLOAD ); // Account for some corner cases with a fallback const homeDir = GLib.get_home_dir(); if (!receiveDir || receiveDir === homeDir) receiveDir = GLib.build_filenamev([homeDir, 'Downloads']); settings.set_string(key, receiveDir); } if (this.receive_directory.get_filename() !== receiveDir) this.receive_directory.set_filename(receiveDir); } _onReceiveDirectorySet(button) { const settings = this.pluginSettings('share'); const receiveDir = settings.get_string('receive-directory'); const filename = button.get_filename(); if (filename !== receiveDir) settings.set_string('receive-directory', filename); } /** * Battery Settings */ async _batterySettings() { try { this.battery_device_list.set_header_func(rowSeparators); this.battery_system_list.set_header_func(rowSeparators); const settings = this.pluginSettings('battery'); const oldLevel = settings.get_uint('custom-battery-notification-value'); this.battery_custom_notification_value.set_value(oldLevel); // If the device can't handle statistics we're done if (!this.get_incoming_supported('battery')) { this.battery_system_label.visible = false; this.battery_system.visible = false; return; } // Check UPower for a battery const hasBattery = await new Promise((resolve, reject) => { Gio.DBus.system.call( 'org.freedesktop.UPower', '/org/freedesktop/UPower/devices/DisplayDevice', 'org.freedesktop.DBus.Properties', 'Get', new GLib.Variant('(ss)', [ 'org.freedesktop.UPower.Device', 'IsPresent', ]), null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { const variant = connection.call_finish(res); const value = variant.deepUnpack()[0]; const isPresent = value.get_boolean(); resolve(isPresent); } catch (e) { resolve(false); } } ); }); this.battery_system_label.visible = hasBattery; this.battery_system.visible = hasBattery; } catch (e) { this.battery_system_label.visible = false; this.battery_system.visible = false; } } _setCustomChargeLevel(spin) { const settings = this.pluginSettings('battery'); settings.set_uint('custom-battery-notification-value', spin.get_value_as_int()); } /** * RunCommand Page */ _runcommandSettings() { // Scroll with keyboard focus const runcommand_box = this.runcommand_page.get_child().get_child(); runcommand_box.set_focus_vadjustment(this.runcommand_page.vadjustment); // Local Command List const settings = this.pluginSettings('runcommand'); this._commands = settings.get_value('command-list').recursiveUnpack(); this.command_list.set_sort_func(this._sortCommands); this.command_list.set_header_func(rowSeparators); for (const uuid of Object.keys(this._commands)) this._insertCommand(uuid); } _sortCommands(row1, row2) { if (!row1.title || !row2.title) return 1; return row1.title.localeCompare(row2.title); } _insertCommand(uuid) { const row = new SectionRow({ title: this._commands[uuid].name, subtitle: this._commands[uuid].command, activatable: false, }); row.set_name(uuid); row.subtitle_label.ellipsize = Pango.EllipsizeMode.MIDDLE; const editButton = new Gtk.Button({ image: new Gtk.Image({ icon_name: 'document-edit-symbolic', pixel_size: 16, visible: true, }), tooltip_text: _('Edit'), valign: Gtk.Align.CENTER, vexpand: true, visible: true, }); editButton.connect('clicked', this._onEditCommand.bind(this)); editButton.get_accessible().set_name(_('Edit')); row.get_child().attach(editButton, 2, 0, 1, 2); const deleteButton = new Gtk.Button({ image: new Gtk.Image({ icon_name: 'edit-delete-symbolic', pixel_size: 16, visible: true, }), tooltip_text: _('Remove'), valign: Gtk.Align.CENTER, vexpand: true, visible: true, }); deleteButton.connect('clicked', this._onDeleteCommand.bind(this)); deleteButton.get_accessible().set_name(_('Remove')); row.get_child().attach(deleteButton, 3, 0, 1, 2); this.command_list.add(row); } _onEditCommand(widget) { if (this._commandEditor === undefined) { this._commandEditor = new CommandEditor({ modal: true, transient_for: this.get_toplevel(), use_header_bar: true, }); this._commandEditor.connect( 'response', this._onSaveCommand.bind(this) ); this._commandEditor.resize(1, 1); } if (widget instanceof Gtk.Button) { const row = widget.get_ancestor(Gtk.ListBoxRow.$gtype); const uuid = row.get_name(); this._commandEditor.uuid = uuid; this._commandEditor.command_name = this._commands[uuid].name; this._commandEditor.command_line = this._commands[uuid].command; } else { this._commandEditor.uuid = GLib.uuid_string_random(); this._commandEditor.command_name = ''; this._commandEditor.command_line = ''; } this._commandEditor.present(); } _storeCommands() { const variant = {}; for (const [uuid, command] of Object.entries(this._commands)) variant[uuid] = new GLib.Variant('a{ss}', command); this.pluginSettings('runcommand').set_value( 'command-list', new GLib.Variant('a{sv}', variant) ); } _onDeleteCommand(button) { const row = button.get_ancestor(Gtk.ListBoxRow.$gtype); delete this._commands[row.get_name()]; row.destroy(); this._storeCommands(); } _onSaveCommand(dialog, response_id) { if (response_id === Gtk.ResponseType.ACCEPT) { this._commands[dialog.uuid] = { name: dialog.command_name, command: dialog.command_line, }; this._storeCommands(); // let row = null; for (const child of this.command_list.get_children()) { if (child.get_name() === dialog.uuid) { row = child; break; } } if (row === null) { this._insertCommand(dialog.uuid); } else { row.set_name(dialog.uuid); row.title = dialog.command_name; row.subtitle = dialog.command_line; } } dialog.hide(); } /** * Notification Settings */ _notificationSettings() { const settings = this.pluginSettings('notification'); settings.bind( 'send-notifications', this.notification_apps, 'sensitive', Gio.SettingsBindFlags.DEFAULT ); // Separators & Sorting this.notification_list.set_header_func(rowSeparators); // Scroll with keyboard focus const notification_box = this.notification_page.get_child().get_child(); notification_box.set_focus_vadjustment(this.notification_page.vadjustment); // Continue focus chain between lists this.notification_list.next = this.notification_apps; this.notification_apps.prev = this.notification_list; this.notification_apps.set_sort_func(titleSortFunc); this.notification_apps.set_header_func(rowSeparators); this._populateApplications(settings); } _toggleNotification(widget) { try { const row = widget.get_ancestor(Gtk.ListBoxRow.$gtype); const settings = this.pluginSettings('notification'); let applications = {}; try { applications = JSON.parse(settings.get_string('applications')); } catch (e) { applications = {}; } applications[row.title].enabled = !applications[row.title].enabled; row.widget.state = applications[row.title].enabled; settings.set_string('applications', JSON.stringify(applications)); } catch (e) { logError(e); } } _populateApplications(settings) { const applications = this._queryApplications(settings); for (const name in applications) { const row = new SectionRow({ gicon: Gio.Icon.new_for_string(applications[name].iconName), title: name, height_request: 48, widget: new Gtk.Switch({ state: applications[name].enabled, margin_start: 12, margin_end: 12, halign: Gtk.Align.END, valign: Gtk.Align.CENTER, vexpand: true, visible: true, }), }); row.widget.connect('notify::active', this._toggleNotification.bind(this)); this.notification_apps.add(row); } } _queryApplications(settings) { let applications = {}; try { applications = JSON.parse(settings.get_string('applications')); } catch (e) { applications = {}; } // Scan applications that statically declare to show notifications const ignoreId = 'org.gnome.Shell.Extensions.GSConnect.desktop'; for (const appInfo of Gio.AppInfo.get_all()) { if (appInfo.get_id() === ignoreId) continue; if (!appInfo.get_boolean('X-GNOME-UsesNotifications')) continue; const appName = appInfo.get_name(); if (appName === null || applications.hasOwnProperty(appName)) continue; let icon = appInfo.get_icon(); icon = (icon) ? icon.to_string() : 'application-x-executable'; applications[appName] = { iconName: icon, enabled: true, }; } settings.set_string('applications', JSON.stringify(applications)); return applications; } /** * Telephony Settings */ _telephonySettings() { // Continue focus chain between lists this.ringing_list.next = this.talking_list; this.talking_list.prev = this.ringing_list; this.ringing_list.set_header_func(rowSeparators); this.talking_list.set_header_func(rowSeparators); } /** * Keyboard Shortcuts */ _keybindingSettings() { // Scroll with keyboard focus const shortcuts_box = this.shortcuts_page.get_child().get_child(); shortcuts_box.set_focus_vadjustment(this.shortcuts_page.vadjustment); // Filter & Sort this.shortcuts_actions_list.set_filter_func(this._filterPluginKeybindings.bind(this)); this.shortcuts_actions_list.set_header_func(rowSeparators); this.shortcuts_actions_list.set_sort_func(titleSortFunc); // Init for (const name in DEVICE_SHORTCUTS) this._addPluginKeybinding(name); this._setPluginKeybindings(); // Watch for GAction and Keybinding changes this._actionAddedId = this.device.action_group.connect( 'action-added', () => this.shortcuts_actions_list.invalidate_filter() ); this._actionRemovedId = this.device.action_group.connect( 'action-removed', () => this.shortcuts_actions_list.invalidate_filter() ); this._keybindingsId = this.settings.connect( 'changed::keybindings', this._setPluginKeybindings.bind(this) ); } _addPluginKeybinding(name) { const [icon_name, label] = DEVICE_SHORTCUTS[name]; const widget = new Gtk.Label({ label: _('Disabled'), visible: true, }); widget.get_style_context().add_class('dim-label'); const row = new SectionRow({ height_request: 48, icon_name: icon_name, title: label, widget: widget, }); row.icon_image.pixel_size = 16; row.action = name; this.shortcuts_actions_list.add(row); } _filterPluginKeybindings(row) { return this.device.action_group.has_action(row.action); } _setPluginKeybindings() { const keybindings = this.settings.get_value('keybindings').deepUnpack(); this.shortcuts_actions_list.foreach(row => { if (keybindings[row.action]) { const accel = Gtk.accelerator_parse(keybindings[row.action]); row.widget.label = Gtk.accelerator_get_label(...accel); } else { row.widget.label = _('Disabled'); } }); } _onResetActionShortcuts(button) { const keybindings = this.settings.get_value('keybindings').deepUnpack(); for (const action in keybindings) { // Don't reset remote command shortcuts if (!action.includes('::')) delete keybindings[action]; } this.settings.set_value( 'keybindings', new GLib.Variant('a{ss}', keybindings) ); } async _onShortcutRowActivated(box, row) { try { const keybindings = this.settings.get_value('keybindings').deepUnpack(); let accel = keybindings[row.action] || null; accel = await Keybindings.getAccelerator(row.title, accel); if (accel) keybindings[row.action] = accel; else delete keybindings[row.action]; this.settings.set_value( 'keybindings', new GLib.Variant('a{ss}', keybindings) ); } catch (e) { logError(e); } } /** * Advanced Page */ _advancedSettings() { // Scroll with keyboard focus const advanced_box = this.advanced_page.get_child().get_child(); advanced_box.set_focus_vadjustment(this.advanced_page.vadjustment); // Sort & Separate this.plugin_list.set_header_func(rowSeparators); this.plugin_list.set_sort_func(titleSortFunc); this.experimental_list.set_header_func(rowSeparators); // Continue focus chain between lists this.plugin_list.next = this.experimental_list; this.experimental_list.prev = this.plugin_list; this._disabledPluginsId = this.settings.connect( 'changed::disabled-plugins', this._onPluginsChanged.bind(this) ); this._supportedPluginsId = this.settings.connect( 'changed::supported-plugins', this._onPluginsChanged.bind(this) ); this._onPluginsChanged(this.settings, null); for (const name of DEVICE_PLUGINS) this._addPlugin(name); } _onPluginsChanged(settings, key) { if (key === 'disabled-plugins' || this._disabledPlugins === undefined) this._disabledPlugins = settings.get_strv('disabled-plugins'); if (key === 'supported-plugins' || this._supportedPlugins === undefined) this._supportedPlugins = settings.get_strv('supported-plugins'); this._enabledPlugins = this._supportedPlugins.filter(name => { return !this._disabledPlugins.includes(name); }); if (key !== null) this._updatePlugins(); } _addPlugin(name) { const plugin = plugins[name]; const row = new SectionRow({ height_request: 48, title: plugin.Metadata.label, subtitle: plugin.Metadata.description || '', visible: this._supportedPlugins.includes(name), widget: new Gtk.Switch({ active: this._enabledPlugins.includes(name), valign: Gtk.Align.CENTER, vexpand: true, visible: true, }), }); row.widget.connect('notify::active', this._togglePlugin.bind(this)); row.set_name(name); if (this.hasOwnProperty(name)) this[name].visible = row.widget.active; this.plugin_list.add(row); } _updatePlugins(settings, key) { for (const row of this.plugin_list.get_children()) { const name = row.get_name(); row.visible = this._supportedPlugins.includes(name); row.widget.active = this._enabledPlugins.includes(name); if (this.hasOwnProperty(name)) this[name].visible = row.widget.active; } } _togglePlugin(widget) { try { const name = widget.get_ancestor(Gtk.ListBoxRow.$gtype).get_name(); const index = this._disabledPlugins.indexOf(name); // Either add or remove the plugin from the disabled list if (index > -1) this._disabledPlugins.splice(index, 1); else this._disabledPlugins.push(name); this.settings.set_strv('disabled-plugins', this._disabledPlugins); } catch (e) { logError(e); } } }); GSConnect-gnome-shell-extension-gsconnect-43258f9/src/preferences/init.js000066400000000000000000000005241460766671100264360ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import setup, {setupGettext} from '../utils/setup.js'; // Bootstrap setup(GLib.path_get_dirname(GLib.path_get_dirname(GLib.filename_from_uri(import.meta.url)[0]))); setupGettext(); GSConnect-gnome-shell-extension-gsconnect-43258f9/src/preferences/keybindings.js000066400000000000000000000217371460766671100300120ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; /* * A list of modifier keysyms we ignore */ const _MODIFIERS = [ Gdk.KEY_Alt_L, Gdk.KEY_Alt_R, Gdk.KEY_Caps_Lock, Gdk.KEY_Control_L, Gdk.KEY_Control_R, Gdk.KEY_Meta_L, Gdk.KEY_Meta_R, Gdk.KEY_Num_Lock, Gdk.KEY_Shift_L, Gdk.KEY_Shift_R, Gdk.KEY_Super_L, Gdk.KEY_Super_R, ]; /** * Response enum for ShortcutChooserDialog */ export const ResponseType = { CANCEL: Gtk.ResponseType.CANCEL, SET: Gtk.ResponseType.APPLY, UNSET: 2, }; /** * A simplified version of the shortcut editor from GNOME Control Center */ export const ShortcutChooserDialog = GObject.registerClass({ GTypeName: 'GSConnectPreferencesShortcutEditor', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/preferences-shortcut-editor.ui', Children: [ 'cancel-button', 'set-button', 'stack', 'summary-label', 'shortcut-label', 'conflict-label', ], }, class ShortcutChooserDialog extends Gtk.Dialog { _init(params) { super._init({ transient_for: Gio.Application.get_default().get_active_window(), use_header_bar: true, }); this._seat = Gdk.Display.get_default().get_default_seat(); // Current accelerator or %null this.accelerator = params.accelerator; // TRANSLATORS: Summary of a keyboard shortcut function // Example: Enter a new shortcut to change Messaging this.summary = _('Enter a new shortcut to change %s').format( params.summary ); } get accelerator() { return this.shortcut_label.accelerator; } set accelerator(value) { this.shortcut_label.accelerator = value; } get summary() { return this.summary_label.label; } set summary(value) { this.summary_label.label = value; } vfunc_key_press_event(event) { let keyvalLower = Gdk.keyval_to_lower(event.keyval); let realMask = event.state & Gtk.accelerator_get_default_mod_mask(); // TODO: Critical: 'WIDGET_REALIZED_FOR_EVENT (widget, event)' failed if (_MODIFIERS.includes(keyvalLower)) return true; // Normalize Tab if (keyvalLower === Gdk.KEY_ISO_Left_Tab) keyvalLower = Gdk.KEY_Tab; // Put shift back if it changed the case of the key, not otherwise. if (keyvalLower !== event.keyval) realMask |= Gdk.ModifierType.SHIFT_MASK; // HACK: we don't want to use SysRq as a keybinding (but we do want // Alt+Print), so we avoid translation from Alt+Print to SysRq if (keyvalLower === Gdk.KEY_Sys_Req && (realMask & Gdk.ModifierType.MOD1_MASK) !== 0) keyvalLower = Gdk.KEY_Print; // A single Escape press cancels the editing if (realMask === 0 && keyvalLower === Gdk.KEY_Escape) { this.response(ResponseType.CANCEL); return false; } // Backspace disables the current shortcut if (realMask === 0 && keyvalLower === Gdk.KEY_BackSpace) { this.response(ResponseType.UNSET); return false; } // CapsLock isn't supported as a keybinding modifier, so keep it from // confusing us realMask &= ~Gdk.ModifierType.LOCK_MASK; if (keyvalLower !== 0 && realMask !== 0) { this._ungrab(); // Set the accelerator property/label this.accelerator = Gtk.accelerator_name(keyvalLower, realMask); // TRANSLATORS: When a keyboard shortcut is unavailable // Example: [Ctrl]+[S] is already being used this.conflict_label.label = _('%s is already being used').format( Gtk.accelerator_get_label(keyvalLower, realMask) ); // Show Cancel button and switch to confirm/conflict page this.cancel_button.visible = true; this.stack.visible_child_name = 'confirm'; this._check(); } return true; } async _check() { try { const available = await checkAccelerator(this.accelerator); this.set_button.visible = available; this.conflict_label.visible = !available; } catch (e) { logError(e); this.response(ResponseType.CANCEL); } } _grab() { const success = this._seat.grab( this.get_window(), Gdk.SeatCapabilities.KEYBOARD, true, // owner_events null, // cursor null, // event null ); if (success !== Gdk.GrabStatus.SUCCESS) return this.response(ResponseType.CANCEL); if (!this._seat.get_keyboard() && !this._seat.get_pointer()) return this.response(ResponseType.CANCEL); this.grab_add(); } _ungrab() { this._seat.ungrab(); this.grab_remove(); } // Override to use our own ungrab process response(response_id) { this.hide(); this._ungrab(); return super.response(response_id); } // Override with a non-blocking version of Gtk.Dialog.run() run() { this.show(); // Wait a bit before attempting grab GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => { this._grab(); return GLib.SOURCE_REMOVE; }); } }); /** * Check the availability of an accelerator using GNOME Shell's DBus interface. * * @param {string} accelerator - An accelerator * @param {number} [modeFlags] - Mode Flags * @param {number} [grabFlags] - Grab Flags * @param {boolean} %true if available, %false on error or unavailable */ export async function checkAccelerator(accelerator, modeFlags = 0, grabFlags = 0) { try { let result = false; // Try to grab the accelerator const action = await new Promise((resolve, reject) => { Gio.DBus.session.call( 'org.gnome.Shell', '/org/gnome/Shell', 'org.gnome.Shell', 'GrabAccelerator', new GLib.Variant('(suu)', [accelerator, modeFlags, grabFlags]), null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { res = connection.call_finish(res); resolve(res.deepUnpack()[0]); } catch (e) { reject(e); } } ); }); // If successful, use the result of ungrabbing as our return if (action !== 0) { result = await new Promise((resolve, reject) => { Gio.DBus.session.call( 'org.gnome.Shell', '/org/gnome/Shell', 'org.gnome.Shell', 'UngrabAccelerator', new GLib.Variant('(u)', [action]), null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { res = connection.call_finish(res); resolve(res.deepUnpack()[0]); } catch (e) { reject(e); } } ); }); } return result; } catch (e) { logError(e); return false; } } /** * Show a dialog to get a keyboard shortcut from a user. * * @param {string} summary - A description of the keybinding's function * @param {string} accelerator - An accelerator as taken by Gtk.ShortcutLabel * @return {string} An accelerator or %null if it should be unset. */ export async function getAccelerator(summary, accelerator = null) { try { const dialog = new ShortcutChooserDialog({ summary: summary, accelerator: accelerator, }); accelerator = await new Promise((resolve, reject) => { dialog.connect('response', (dialog, response) => { switch (response) { case ResponseType.SET: accelerator = dialog.accelerator; break; case ResponseType.UNSET: accelerator = null; break; case ResponseType.CANCEL: // leave the accelerator as passed in break; } dialog.destroy(); resolve(accelerator); }); dialog.run(); }); return accelerator; } catch (e) { logError(e); return accelerator; } } GSConnect-gnome-shell-extension-gsconnect-43258f9/src/preferences/service.js000066400000000000000000000452161460766671100271420ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import GdkPixbuf from 'gi://GdkPixbuf'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import system from 'system'; import Config from '../config.js'; import {Panel, rowSeparators} from './device.js'; import {Service} from '../utils/remote.js'; /* * Header for support logs */ const LOG_HEADER = new GLib.Bytes(` GSConnect: ${Config.PACKAGE_VERSION} (${Config.IS_USER ? 'user' : 'system'}) GJS: ${system.version} Session: ${GLib.getenv('XDG_SESSION_TYPE')} OS: ${GLib.get_os_info('PRETTY_NAME')} -------------------------------------------------------------------------------- `); /** * Generate a support log. * * @param {string} time - Start time as a string (24-hour notation) */ async function generateSupportLog(time) { try { const [file, stream] = Gio.File.new_tmp('gsconnect.XXXXXX'); const logFile = stream.get_output_stream(); await new Promise((resolve, reject) => { logFile.write_bytes_async(LOG_HEADER, 0, null, (file, res) => { try { resolve(file.write_bytes_finish(res)); } catch (e) { reject(e); } }); }); // FIXME: BSD??? const proc = new Gio.Subprocess({ flags: (Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_MERGE), argv: ['journalctl', '--no-host', '--since', time], }); proc.init(null); logFile.splice_async( proc.get_stdout_pipe(), Gio.OutputStreamSpliceFlags.CLOSE_TARGET, GLib.PRIORITY_DEFAULT, null, (source, res) => { try { source.splice_finish(res); } catch (e) { logError(e); } } ); await new Promise((resolve, reject) => { proc.wait_check_async(null, (proc, res) => { try { resolve(proc.wait_finish(res)); } catch (e) { reject(e); } }); }); const uri = file.get_uri(); Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null); } catch (e) { logError(e); } } /** * "Connect to..." Dialog */ const ConnectDialog = GObject.registerClass({ GTypeName: 'GSConnectConnectDialog', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/connect-dialog.ui', Children: [ 'cancel-button', 'connect-button', 'lan-grid', 'lan-ip', 'lan-port', ], }, class ConnectDialog extends Gtk.Dialog { _init(params = {}) { super._init(Object.assign({ use_header_bar: true, }, params)); } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { try { let address; // Lan host/port entered if (this.lan_ip.text) { const host = this.lan_ip.text; const port = this.lan_port.value; address = GLib.Variant.new_string(`lan://${host}:${port}`); } else { return false; } this.application.activate_action('connect', address); } catch (e) { logError(e); } } this.destroy(); return false; } }); export const Window = GObject.registerClass({ GTypeName: 'GSConnectPreferencesWindow', Properties: { 'display-mode': GObject.ParamSpec.string( 'display-mode', 'Display Mode', 'Display devices in either the Panel or User Menu', GObject.ParamFlags.READWRITE, null ), }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/preferences-window.ui', Children: [ // HeaderBar 'headerbar', 'infobar', 'stack', 'service-menu', 'service-edit', 'refresh-button', 'device-menu', 'prev-button', // Popover 'rename-popover', 'rename', 'rename-label', 'rename-entry', 'rename-submit', // Focus Box 'service-window', 'service-box', // Device List 'device-list', 'device-list-spinner', 'device-list-placeholder', ], }, class PreferencesWindow extends Gtk.ApplicationWindow { _init(params = {}) { super._init(params); // Service Settings this.settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect', true ), }); // Service Proxy this.service = new Service(); this._deviceAddedId = this.service.connect( 'device-added', this._onDeviceAdded.bind(this) ); this._deviceRemovedId = this.service.connect( 'device-removed', this._onDeviceRemoved.bind(this) ); this._serviceChangedId = this.service.connect( 'notify::active', this._onServiceChanged.bind(this) ); // HeaderBar (Service Name) this.headerbar.title = this.settings.get_string('name'); this.rename_entry.text = this.headerbar.title; // Scroll with keyboard focus this.service_box.set_focus_vadjustment(this.service_window.vadjustment); // Device List this.device_list.set_header_func(rowSeparators); // Discoverable InfoBar this.settings.bind( 'discoverable', this.infobar, 'reveal-child', Gio.SettingsBindFlags.INVERT_BOOLEAN ); this.add_action(this.settings.create_action('discoverable')); // Application Menu this._initMenu(); // Setting: Keep Alive When Locked this.add_action(this.settings.create_action('keep-alive-when-locked')); // Broadcast automatically every 5 seconds if there are no devices yet this._refreshSource = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT, 5, this._refresh.bind(this) ); // Restore window size/maximized/position this._restoreGeometry(); // Prime the service this._initService(); } get display_mode() { if (this.settings.get_boolean('show-indicators')) return 'panel'; return 'user-menu'; } set display_mode(mode) { this.settings.set_boolean('show-indicators', (mode === 'panel')); } vfunc_delete_event(event) { if (this.service) { this.service.disconnect(this._deviceAddedId); this.service.disconnect(this._deviceRemovedId); this.service.disconnect(this._serviceChangedId); this.service.destroy(); this.service = null; } this._saveGeometry(); GLib.source_remove(this._refreshSource); return false; } async _initService() { try { this.refresh_button.grab_focus(); this._onServiceChanged(this.service, null); await this.service.reload(); } catch (e) { logError(e, 'GSConnect'); } } _initMenu() { // Panel/User Menu mode const displayMode = new Gio.PropertyAction({ name: 'display-mode', property_name: 'display-mode', object: this, }); this.add_action(displayMode); // About Dialog const aboutDialog = new Gio.SimpleAction({name: 'about'}); aboutDialog.connect('activate', this._aboutDialog.bind(this)); this.add_action(aboutDialog); // "Connect to..." Dialog const connectDialog = new Gio.SimpleAction({name: 'connect'}); connectDialog.connect('activate', this._connectDialog.bind(this)); this.add_action(connectDialog); // "Generate Support Log" GAction const generateSupportLog = new Gio.SimpleAction({name: 'support-log'}); generateSupportLog.connect('activate', this._generateSupportLog.bind(this)); this.add_action(generateSupportLog); // "Help" GAction const help = new Gio.SimpleAction({name: 'help'}); help.connect('activate', this._help); this.add_action(help); } _refresh() { if (this.service.active && this.device_list.get_children().length < 1) { this.device_list_spinner.active = true; this.service.activate_action('refresh', null); } else { this.device_list_spinner.active = false; } return GLib.SOURCE_CONTINUE; } /* * Window State */ _restoreGeometry() { this._windowState = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect.WindowState', true ), path: '/org/gnome/shell/extensions/gsconnect/preferences/', }); // Size const [width, height] = this._windowState.get_value('window-size').deepUnpack(); if (width && height) this.set_default_size(width, height); // Maximized State if (this._windowState.get_boolean('window-maximized')) this.maximize(); } _saveGeometry() { const state = this.get_window().get_state(); // Maximized State const maximized = (state & Gdk.WindowState.MAXIMIZED); this._windowState.set_boolean('window-maximized', maximized); // Leave the size at the value before maximizing if (maximized || (state & Gdk.WindowState.FULLSCREEN)) return; // Size const size = this.get_size(); this._windowState.set_value('window-size', new GLib.Variant('(ii)', size)); } /** * About Dialog */ _aboutDialog() { if (this._about === undefined) { this._about = new Gtk.AboutDialog({ application: Gio.Application.get_default(), authors: [ 'Andy Holmes ', 'Bertrand Lacoste ', 'Frank Dana ', ], comments: _('A complete KDE Connect implementation for GNOME'), logo: GdkPixbuf.Pixbuf.new_from_resource_at_scale( '/org/gnome/Shell/Extensions/GSConnect/icons/org.gnome.Shell.Extensions.GSConnect.svg', 128, 128, true ), program_name: 'GSConnect', // TRANSLATORS: eg. 'Translator Name ' translator_credits: _('translator-credits'), version: Config.PACKAGE_VERSION.toString(), website: Config.PACKAGE_URL, license_type: Gtk.License.GPL_2_0, modal: true, transient_for: this, }); // Persist this._about.connect('response', (dialog) => dialog.hide_on_delete()); this._about.connect('delete-event', (dialog) => dialog.hide_on_delete()); } this._about.present(); } /** * Connect to..." Dialog */ _connectDialog() { new ConnectDialog({ application: Gio.Application.get_default(), modal: true, transient_for: this, }); } /* * "Generate Support Log" GAction */ _generateSupportLog() { const dialog = new Gtk.MessageDialog({ text: _('Generate Support Log'), secondary_text: _('Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log.'), }); dialog.add_button(_('Cancel'), Gtk.ResponseType.CANCEL); dialog.add_button(_('Review Log'), Gtk.ResponseType.OK); // Enable debug logging and mark the current time this.settings.set_boolean('debug', true); const now = GLib.DateTime.new_now_local().format('%R'); dialog.connect('response', (dialog, response_id) => { // Disable debug logging and destroy the dialog this.settings.set_boolean('debug', false); dialog.destroy(); // Only generate a log if instructed if (response_id === Gtk.ResponseType.OK) generateSupportLog(now); }); dialog.show_all(); } /* * "Help" GAction */ _help(action, parameter) { const uri = `${Config.PACKAGE_URL}/wiki/Help`; Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null); } /* * HeaderBar Callbacks */ _onPrevious(button, event) { // HeaderBar (Service) this.prev_button.visible = false; this.device_menu.visible = false; this.refresh_button.visible = true; this.service_edit.visible = true; this.service_menu.visible = true; this.headerbar.title = this.settings.get_string('name'); this.headerbar.subtitle = null; // Panel this.stack.visible_child_name = 'service'; this._setDeviceMenu(); } _onEditServiceName(button, event) { this.rename_entry.text = this.headerbar.title; this.rename_entry.has_focus = true; } _onSetServiceName(widget) { if (this.rename_entry.text.length) { this.headerbar.title = this.rename_entry.text; this.settings.set_string('name', this.rename_entry.text); } this.service_edit.active = false; this.service_edit.grab_focus(); } /* * Context Switcher */ _getTypeLabel(device) { switch (device.type) { case 'laptop': return _('Laptop'); case 'phone': return _('Smartphone'); case 'tablet': return _('Tablet'); case 'tv': return _('Television'); default: return _('Desktop'); } } _setDeviceMenu(panel = null) { this.device_menu.insert_action_group('device', null); this.device_menu.insert_action_group('settings', null); this.device_menu.set_menu_model(null); if (panel === null) return; this.device_menu.insert_action_group('device', panel.device.action_group); this.device_menu.insert_action_group('settings', panel.actions); this.device_menu.set_menu_model(panel.menu); } _onDeviceChanged(statusLabel, device, pspec) { switch (false) { case device.paired: statusLabel.label = _('Unpaired'); break; case device.connected: statusLabel.label = _('Disconnected'); break; default: statusLabel.label = _('Connected'); } } _createDeviceRow(device) { const row = new Gtk.ListBoxRow({ height_request: 52, selectable: false, visible: true, }); row.set_name(device.id); const grid = new Gtk.Grid({ column_spacing: 12, margin_left: 20, margin_right: 20, margin_bottom: 8, margin_top: 8, visible: true, }); row.add(grid); const icon = new Gtk.Image({ gicon: new Gio.ThemedIcon({name: device.icon_name}), icon_size: Gtk.IconSize.BUTTON, visible: true, }); grid.attach(icon, 0, 0, 1, 1); const title = new Gtk.Label({ halign: Gtk.Align.START, hexpand: true, valign: Gtk.Align.CENTER, vexpand: true, visible: true, }); grid.attach(title, 1, 0, 1, 1); const status = new Gtk.Label({ halign: Gtk.Align.END, hexpand: true, valign: Gtk.Align.CENTER, vexpand: true, visible: true, }); grid.attach(status, 2, 0, 1, 1); // Keep name up to date device.bind_property( 'name', title, 'label', GObject.BindingFlags.SYNC_CREATE ); // Keep status up to date device.connect( 'notify::connected', this._onDeviceChanged.bind(null, status) ); device.connect( 'notify::paired', this._onDeviceChanged.bind(null, status) ); this._onDeviceChanged(status, device, null); return row; } _onDeviceAdded(service, device) { try { if (!this.stack.get_child_by_name(device.id)) { // Add the device preferences const prefs = new Panel(device); this.stack.add_titled(prefs, device.id, device.name); // Add a row to the device list prefs.row = this._createDeviceRow(device); this.device_list.add(prefs.row); } } catch (e) { logError(e); } } _onDeviceRemoved(service, device) { try { const prefs = this.stack.get_child_by_name(device.id); if (prefs === null) return; if (prefs === this.stack.get_visible_child()) this._onPrevious(); prefs.row.destroy(); prefs.row = null; prefs.dispose(); prefs.destroy(); } catch (e) { logError(e); } } _onDeviceSelected(box, row) { try { if (row === null) return this._onPrevious(); // Transition the panel const name = row.get_name(); const prefs = this.stack.get_child_by_name(name); this.stack.visible_child = prefs; this._setDeviceMenu(prefs); // HeaderBar (Device) this.refresh_button.visible = false; this.service_edit.visible = false; this.service_menu.visible = false; this.prev_button.visible = true; this.device_menu.visible = true; this.headerbar.title = prefs.device.name; this.headerbar.subtitle = this._getTypeLabel(prefs.device); } catch (e) { logError(e); } } _onServiceChanged(service, pspec) { if (this.service.active) this.device_list_placeholder.label = _('Searching for devices…'); else this.device_list_placeholder.label = _('Waiting for service…'); } }); GSConnect-gnome-shell-extension-gsconnect-43258f9/src/prefs.js000066400000000000000000000016061460766671100243130ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Adw from 'gi://Adw'; // Bootstrap import * as Utils from './shell/utils.js'; import setup from './utils/setup.js'; import {ExtensionPreferences} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; export default class GSConnectExtensionPreferences extends ExtensionPreferences { constructor(metadata) { super(metadata); setup(this.path); Utils.installService(); } fillPreferencesWindow(window) { const widget = new Adw.PreferencesPage(); window.add(widget); GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { window.close(); }); Gio.Subprocess.new([`${this.path}/gsconnect-preferences`], 0); } } GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/000077500000000000000000000000001460766671100242735ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/backends/000077500000000000000000000000001460766671100260455ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/backends/lan.js000066400000000000000000000655621460766671100271730ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Config from '../../config.js'; import * as Core from '../core.js'; // Retain compatibility with GLib < 2.80, which lacks GioUnix let GioUnix; try { GioUnix = (await import('gi://GioUnix')).default; } catch (e) { GioUnix = { InputStream: Gio.UnixInputStream, OutputStream: Gio.UnixOutputStream, }; } /** * TCP Port Constants */ const PROTOCOL_PORT_DEFAULT = 1716; const PROTOCOL_PORT_MIN = 1716; const PROTOCOL_PORT_MAX = 1764; const TRANSFER_MIN = 1739; const TRANSFER_MAX = 1764; /* * One-time check for Linux/FreeBSD socket options */ export let _LINUX_SOCKETS = true; try { // This should throw on FreeBSD Gio.Socket.new( Gio.SocketFamily.IPV4, Gio.SocketType.STREAM, Gio.SocketProtocol.TCP ).get_option(6, 5); } catch (e) { _LINUX_SOCKETS = false; } /** * Configure a socket connection for the KDE Connect protocol. * * @param {Gio.SocketConnection} connection - The connection to configure */ export function _configureSocket(connection) { try { if (_LINUX_SOCKETS) { connection.socket.set_option(6, 4, 10); // TCP_KEEPIDLE connection.socket.set_option(6, 5, 5); // TCP_KEEPINTVL connection.socket.set_option(6, 6, 3); // TCP_KEEPCNT // FreeBSD constants // https://github.com/freebsd/freebsd/blob/master/sys/netinet/tcp.h#L159 } else { connection.socket.set_option(6, 256, 10); // TCP_KEEPIDLE connection.socket.set_option(6, 512, 5); // TCP_KEEPINTVL connection.socket.set_option(6, 1024, 3); // TCP_KEEPCNT } // Do this last because an error setting the keepalive options would // result in a socket that never times out connection.socket.set_keepalive(true); } catch (e) { debug(e, 'Configuring Socket'); } } /** * Lan.ChannelService consists of two parts: * * The TCP Listener listens on a port and constructs a Channel object from the * incoming Gio.TcpConnection. * * The UDP Listener listens on a port for incoming JSON identity packets which * include the TCP port, while the IP address is taken from the UDP packet * itself. We respond by opening a TCP connection to that address. */ export const ChannelService = GObject.registerClass({ GTypeName: 'GSConnectLanChannelService', Properties: { 'certificate': GObject.ParamSpec.object( 'certificate', 'Certificate', 'The TLS certificate', GObject.ParamFlags.READWRITE, Gio.TlsCertificate.$gtype ), 'port': GObject.ParamSpec.uint( 'port', 'Port', 'The port used by the service', GObject.ParamFlags.READWRITE, 0, GLib.MAXUINT16, PROTOCOL_PORT_DEFAULT ), }, }, class LanChannelService extends Core.ChannelService { _init(params = {}) { super._init(params); // Track hosts we identify to directly, allowing them to ignore the // discoverable state of the service. this._allowed = new Set(); // this._tcp = null; this._tcpPort = PROTOCOL_PORT_DEFAULT; this._udp4 = null; this._udp6 = null; // Monitor network status this._networkMonitor = Gio.NetworkMonitor.get_default(); this._networkAvailable = false; this._networkChangedId = 0; } get certificate() { if (this._certificate === undefined) this._certificate = null; return this._certificate; } set certificate(certificate) { if (this.certificate === certificate) return; this._certificate = certificate; this.notify('certificate'); } get channels() { if (this._channels === undefined) this._channels = new Map(); return this._channels; } get port() { if (this._port === undefined) this._port = PROTOCOL_PORT_DEFAULT; return this._port; } set port(port) { if (this.port === port) return; this._port = port; this.notify('port'); } _onNetworkChanged(monitor, network_available) { if (this._networkAvailable === network_available) return; this._networkAvailable = network_available; this.broadcast(); } _initCertificate() { if (GLib.find_program_in_path(Config.OPENSSL_PATH) === null) { const error = new Error(); error.name = _('OpenSSL not found'); error.url = `${Config.PACKAGE_URL}/wiki/Error#openssl-not-found`; throw error; } const certPath = GLib.build_filenamev([ Config.CONFIGDIR, 'certificate.pem', ]); const keyPath = GLib.build_filenamev([ Config.CONFIGDIR, 'private.pem', ]); // Ensure a certificate exists with our id as the common name this._certificate = Gio.TlsCertificate.new_for_paths(certPath, keyPath, this.id); // If the service ID doesn't match the common name, this is probably a // certificate from an older version and we should amend ours to match if (this.id !== this._certificate.common_name) this._id = this._certificate.common_name; } _initTcpListener() { try { this._tcp = new Gio.SocketService(); let tcpPort = this.port; const tcpPortMax = tcpPort + (PROTOCOL_PORT_MAX - PROTOCOL_PORT_MIN); while (tcpPort <= tcpPortMax) { try { this._tcp.add_inet_port(tcpPort, null); break; } catch (e) { if (tcpPort < tcpPortMax) { tcpPort++; continue; } throw e; } } this._tcpPort = tcpPort; this._tcp.connect('incoming', this._onIncomingChannel.bind(this)); } catch (e) { this._tcp.stop(); this._tcp.close(); this._tcp = null; throw e; } } async _onIncomingChannel(listener, connection) { try { const host = connection.get_remote_address().address.to_string(); // Create a channel const channel = new Channel({ backend: this, certificate: this.certificate, host: host, port: this.port, }); // Accept the connection await channel.accept(connection); channel.identity.body.tcpHost = channel.host; channel.identity.body.tcpPort = this._tcpPort; channel.allowed = this._allowed.has(host); this.channel(channel); } catch (e) { debug(e); } } _initUdpListener() { // Default broadcast address this._udp_address = Gio.InetSocketAddress.new_from_string( '255.255.255.255', this.port); try { this._udp6 = Gio.Socket.new(Gio.SocketFamily.IPV6, Gio.SocketType.DATAGRAM, Gio.SocketProtocol.UDP); this._udp6.set_broadcast(true); // Bind the socket const inetAddr = Gio.InetAddress.new_any(Gio.SocketFamily.IPV6); const sockAddr = Gio.InetSocketAddress.new(inetAddr, this.port); this._udp6.bind(sockAddr, true); // Input stream this._udp6_stream = new Gio.DataInputStream({ base_stream: new GioUnix.InputStream({ fd: this._udp6.fd, close_fd: false, }), }); // Watch socket for incoming packets this._udp6_source = this._udp6.create_source(GLib.IOCondition.IN, null); this._udp6_source.set_callback(this._onIncomingIdentity.bind(this, this._udp6)); this._udp6_source.attach(null); } catch (e) { this._udp6 = null; } // Our IPv6 socket also supports IPv4; we're all done if (this._udp6 && this._udp6.speaks_ipv4()) { this._udp4 = null; return; } try { this._udp4 = Gio.Socket.new(Gio.SocketFamily.IPV4, Gio.SocketType.DATAGRAM, Gio.SocketProtocol.UDP); this._udp4.set_broadcast(true); // Bind the socket const inetAddr = Gio.InetAddress.new_any(Gio.SocketFamily.IPV4); const sockAddr = Gio.InetSocketAddress.new(inetAddr, this.port); this._udp4.bind(sockAddr, true); // Input stream this._udp4_stream = new Gio.DataInputStream({ base_stream: new GioUnix.InputStream({ fd: this._udp4.fd, close_fd: false, }), }); // Watch input socket for incoming packets this._udp4_source = this._udp4.create_source(GLib.IOCondition.IN, null); this._udp4_source.set_callback(this._onIncomingIdentity.bind(this, this._udp4)); this._udp4_source.attach(null); } catch (e) { this._udp4 = null; // We failed to get either an IPv4 or IPv6 socket to bind if (this._udp6 === null) throw e; } } _onIncomingIdentity(socket) { let host; // Try to peek the remote address try { host = socket.receive_message([], Gio.SocketMsgFlags.PEEK, null)[1] .address.to_string(); } catch (e) { logError(e); } // Whether or not we peeked the address, we need to read the packet try { let data; if (socket === this._udp6) data = this._udp6_stream.read_line_utf8(null)[0]; else data = this._udp4_stream.read_line_utf8(null)[0]; // Discard the packet if we failed to peek the address if (host === undefined) return GLib.SOURCE_CONTINUE; const packet = new Core.Packet(data); packet.body.tcpHost = host; this._onIdentity(packet); } catch (e) { logError(e); } return GLib.SOURCE_CONTINUE; } async _onIdentity(packet) { try { // Bail if the deviceId is missing if (!packet.body.hasOwnProperty('deviceId')) return; // Silently ignore our own broadcasts if (packet.body.deviceId === this.identity.body.deviceId) return; debug(packet); // Create a new channel const channel = new Channel({ backend: this, certificate: this.certificate, host: packet.body.tcpHost, port: packet.body.tcpPort, identity: packet, }); // Check if channel is already open with this address if (this.channels.has(channel.address)) return; this._channels.set(channel.address, channel); // Open a TCP connection const address = Gio.InetSocketAddress.new_from_string( packet.body.tcpHost, packet.body.tcpPort); const client = new Gio.SocketClient({enable_proxy: false}); const connection = await client.connect_async(address, this.cancellable); // Connect the channel and attach it to the device on success await channel.open(connection); this.channel(channel); } catch (e) { logError(e); } } /** * Broadcast an identity packet * * If @address is not %null it may specify an IPv4 or IPv6 address to send * the identity packet directly to, otherwise it will be broadcast to the * default address, 255.255.255.255. * * @param {string} [address] - An optional target IPv4 or IPv6 address */ broadcast(address = null) { try { if (!this._networkAvailable) return; // Try to parse strings as : if (typeof address === 'string') { const [host, portstr] = address.split(':'); const port = parseInt(portstr) || this.port; address = Gio.InetSocketAddress.new_from_string(host, port); } // If we succeed, remember this host if (address instanceof Gio.InetSocketAddress) { this._allowed.add(address.address.to_string()); // Broadcast to the network if no address is specified } else { debug('Broadcasting to LAN'); address = this._udp_address; } // Broadcast on each open socket if (this._udp6 !== null) this._udp6.send_to(address, this.identity.serialize(), null); if (this._udp4 !== null) this._udp4.send_to(address, this.identity.serialize(), null); } catch (e) { debug(e, address); } } buildIdentity() { // Chain-up, then add the TCP port super.buildIdentity(); this.identity.body.tcpPort = this._tcpPort; } start() { if (this.active) return; // Ensure a certificate exists if (this.certificate === null) this._initCertificate(); // Start TCP/UDP listeners try { if (this._tcp === null) this._initTcpListener(); if (this._udp4 === null && this._udp6 === null) this._initUdpListener(); } catch (e) { // Known case of another application using the protocol defined port if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.ADDRESS_IN_USE)) { e.name = _('Port already in use'); e.url = `${Config.PACKAGE_URL}/wiki/Error#port-already-in-use`; } throw e; } // Monitor network changes if (this._networkChangedId === 0) { this._networkAvailable = this._networkMonitor.network_available; this._networkChangedId = this._networkMonitor.connect( 'network-changed', this._onNetworkChanged.bind(this)); } this._active = true; this.notify('active'); } stop() { if (this._networkChangedId) { this._networkMonitor.disconnect(this._networkChangedId); this._networkChangedId = 0; this._networkAvailable = false; } if (this._tcp !== null) { this._tcp.stop(); this._tcp.close(); this._tcp = null; } if (this._udp6 !== null) { this._udp6_source.destroy(); this._udp6_stream.close(null); this._udp6.close(); this._udp6 = null; } if (this._udp4 !== null) { this._udp4_source.destroy(); this._udp4_stream.close(null); this._udp4.close(); this._udp4 = null; } for (const channel of this.channels.values()) channel.close(); this._active = false; this.notify('active'); } destroy() { try { this.stop(); } catch (e) { debug(e); } } }); /** * Lan Channel * * This class essentially just extends Core.Channel to set TCP socket options * and negotiate TLS encrypted connections. */ export const Channel = GObject.registerClass({ GTypeName: 'GSConnectLanChannel', }, class LanChannel extends Core.Channel { _init(params) { super._init(); Object.assign(this, params); } get address() { return `lan://${this.host}:${this.port}`; } get certificate() { if (this._certificate === undefined) this._certificate = null; return this._certificate; } set certificate(certificate) { this._certificate = certificate; } get peer_certificate() { if (this._connection instanceof Gio.TlsConnection) return this._connection.get_peer_certificate(); return null; } get host() { if (this._host === undefined) this._host = null; return this._host; } set host(host) { this._host = host; } get port() { if (this._port === undefined) { if (this.identity && this.identity.body.tcpPort) this._port = this.identity.body.tcpPort; else return PROTOCOL_PORT_DEFAULT; } return this._port; } set port(port) { this._port = port; } /** * Authenticate a TLS connection. * * @param {Gio.TlsConnection} connection - A TLS connection * @return {Promise} A promise for the operation */ async _authenticate(connection) { // Standard TLS Handshake connection.validation_flags = Gio.TlsCertificateFlags.EXPIRED; connection.authentication_mode = Gio.TlsAuthenticationMode.REQUIRED; await connection.handshake_async(GLib.PRIORITY_DEFAULT, this.cancellable); // Get a settings object for the device let settings; if (this.device) { settings = this.device.settings; } else { const id = this.identity.body.deviceId; settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect.Device', true ), path: `/org/gnome/shell/extensions/gsconnect/device/${id}/`, }); } // If we have a certificate for this deviceId, we can verify it const cert_pem = settings.get_string('certificate-pem'); if (cert_pem !== '') { let certificate = null; let verified = false; try { certificate = Gio.TlsCertificate.new_from_pem(cert_pem, -1); verified = certificate.is_same(connection.peer_certificate); } catch (e) { logError(e); } /* The certificate is incorrect for one of two reasons, but both * result in us resetting the certificate and unpairing the device. * * If the certificate failed to load, it is probably corrupted or * otherwise invalid. In this case, if we try to continue we will * certainly crash the Android app. * * If the certificate did not match what we expected the obvious * thing to do is to notify the user, however experience tells us * this is a result of the user doing something masochistic like * nuking the Android app data or copying settings between machines. */ if (verified === false) { if (this.device) { this.device.unpair(); } else { settings.reset('paired'); settings.reset('certificate-pem'); } const name = this.identity.body.deviceName; throw new Error(`${name}: Authentication Failure`); } } return connection; } /** * Wrap the connection in Gio.TlsClientConnection and initiate handshake * * @param {Gio.TcpConnection} connection - The unauthenticated connection * @return {Gio.TlsClientConnection} The authenticated connection */ _encryptClient(connection) { _configureSocket(connection); connection = Gio.TlsClientConnection.new(connection, connection.socket.remote_address); connection.set_certificate(this.certificate); return this._authenticate(connection); } /** * Wrap the connection in Gio.TlsServerConnection and initiate handshake * * @param {Gio.TcpConnection} connection - The unauthenticated connection * @return {Gio.TlsServerConnection} The authenticated connection */ _encryptServer(connection) { _configureSocket(connection); connection = Gio.TlsServerConnection.new(connection, this.certificate); // We're the server so we trust-on-first-use and verify after const _id = connection.connect('accept-certificate', (connection) => { connection.disconnect(_id); return true; }); return this._authenticate(connection); } /** * Negotiate an incoming connection * * @param {Gio.TcpConnection} connection - The incoming connection */ async accept(connection) { debug(`${this.address} (${this.uuid})`); try { this._connection = connection; this.backend.channels.set(this.address, this); // In principle this disposable wrapper could buffer more than the // identity packet, but in practice the remote device shouldn't send // any more data until the TLS connection is negotiated. const stream = new Gio.DataInputStream({ base_stream: connection.input_stream, close_base_stream: false, }); const data = await stream.read_line_async(GLib.PRIORITY_DEFAULT, this.cancellable); stream.close_async(GLib.PRIORITY_DEFAULT, null, null); this.identity = new Core.Packet(data[0]); if (!this.identity.body.deviceId) throw new Error('missing deviceId'); this._connection = await this._encryptClient(connection); } catch (e) { this.close(); throw e; } } /** * Negotiate an outgoing connection * * @param {Gio.SocketConnection} connection - The remote connection */ async open(connection) { debug(`${this.address} (${this.uuid})`); try { this._connection = connection; this.backend.channels.set(this.address, this); await connection.get_output_stream().write_all_async( this.backend.identity.serialize(), GLib.PRIORITY_DEFAULT, this.cancellable); this._connection = await this._encryptServer(connection); } catch (e) { this.close(); throw e; } } /** * Close all streams associated with this channel, silencing any errors */ close() { if (this.closed) return; debug(`${this.address} (${this.uuid})`); this._closed = true; this.notify('closed'); this.backend.channels.delete(this.address); this.cancellable.cancel(); if (this._connection) this._connection.close_async(GLib.PRIORITY_DEFAULT, null, null); if (this.input_stream) this.input_stream.close_async(GLib.PRIORITY_DEFAULT, null, null); if (this.output_stream) this.output_stream.close_async(GLib.PRIORITY_DEFAULT, null, null); } async download(packet, target, cancellable = null) { const address = Gio.InetSocketAddress.new_from_string(this.host, packet.payloadTransferInfo.port); const client = new Gio.SocketClient({enable_proxy: false}); const connection = await client.connect_async(address, cancellable) .then(this._encryptClient.bind(this)); // Start the transfer const transferredSize = await target.splice_async( connection.input_stream, (Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET), GLib.PRIORITY_DEFAULT, cancellable); // If we get less than expected, we've certainly got corruption if (transferredSize < packet.payloadSize) { throw new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.FAILED, message: `Incomplete: ${transferredSize}/${packet.payloadSize}`, }); // TODO: sometimes kdeconnect-android under-reports a file's size // https://github.com/GSConnect/gnome-shell-extension-gsconnect/issues/1157 } else if (transferredSize > packet.payloadSize) { logError(new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.FAILED, message: `Extra Data: ${transferredSize - packet.payloadSize}`, })); } } async upload(packet, source, size, cancellable = null) { // Start listening on the first available port between 1739-1764 const listener = new Gio.SocketListener(); let port = TRANSFER_MIN; while (port <= TRANSFER_MAX) { try { listener.add_inet_port(port, null); break; } catch (e) { if (port < TRANSFER_MAX) { port++; continue; } else { throw e; } } } // Listen for the incoming connection const acceptConnection = listener.accept_async(cancellable) .then(result => this._encryptServer(result[0])); // Create an upload request packet.body.payloadHash = this.checksum; packet.payloadSize = size; packet.payloadTransferInfo = {port: port}; const requestUpload = this.sendPacket(new Core.Packet(packet), cancellable); // Request an upload stream, accept the connection and get the output const [, connection] = await Promise.all([requestUpload, acceptConnection]); // Start the transfer const transferredSize = await connection.output_stream.splice_async( source, (Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET), GLib.PRIORITY_DEFAULT, cancellable); if (transferredSize !== size) { throw new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.PARTIAL_INPUT, message: 'Transfer incomplete', }); } } async rejectTransfer(packet) { try { if (!packet || !packet.hasPayload()) return; if (packet.payloadTransferInfo.port === undefined) return; const address = Gio.InetSocketAddress.new_from_string(this.host, packet.payloadTransferInfo.port); const client = new Gio.SocketClient({enable_proxy: false}); const connection = await client.connect_async(address, null) .then(this._encryptClient.bind(this)); connection.close_async(GLib.PRIORITY_DEFAULT, null, null); } catch (e) { debug(e, this.device.name); } } }); GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/000077500000000000000000000000001460766671100264605ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/atspi.js000066400000000000000000000222131460766671100301360ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Atspi from 'gi://Atspi?version=2.0'; import Gdk from 'gi://Gdk'; /** * Printable ASCII range */ const _ASCII = /[\x20-\x7E]/; /** * Modifier Keycode Defaults */ const XKeycode = { Alt_L: 0x40, Control_L: 0x25, Shift_L: 0x32, Super_L: 0x85, }; /** * A thin wrapper around Atspi for X11 sessions without Pipewire support. */ export default class Controller { constructor() { // Atspi.init() return 2 on fail, but still marks itself as inited. We // uninit before throwing an error otherwise any future call to init() // will appear successful and other calls will cause GSConnect to exit. // See: https://gitlab.gnome.org/GNOME/at-spi2-core/blob/master/atspi/atspi-misc.c if (Atspi.init() === 2) { this.destroy(); throw new Error('Failed to start AT-SPI'); } try { this._display = Gdk.Display.get_default(); this._seat = this._display.get_default_seat(); this._pointer = this._seat.get_pointer(); } catch (e) { this.destroy(); throw e; } // Try to read modifier keycodes from Gdk try { const keymap = Gdk.Keymap.get_for_display(this._display); let modifier; modifier = keymap.get_entries_for_keyval(Gdk.KEY_Alt_L)[1][0]; XKeycode.Alt_L = modifier.keycode; modifier = keymap.get_entries_for_keyval(Gdk.KEY_Control_L)[1][0]; XKeycode.Control_L = modifier.keycode; modifier = keymap.get_entries_for_keyval(Gdk.KEY_Shift_L)[1][0]; XKeycode.Shift_L = modifier.keycode; modifier = keymap.get_entries_for_keyval(Gdk.KEY_Super_L)[1][0]; XKeycode.Super_L = modifier.keycode; } catch (e) { debug('using default modifier keycodes'); } } /* * Pointer events */ clickPointer(button) { try { const [, x, y] = this._pointer.get_position(); const monitor = this._display.get_monitor_at_point(x, y); const scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}c`); } catch (e) { logError(e); } } doubleclickPointer(button) { try { const [, x, y] = this._pointer.get_position(); const monitor = this._display.get_monitor_at_point(x, y); const scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}d`); } catch (e) { logError(e); } } movePointer(dx, dy) { try { const [, x, y] = this._pointer.get_position(); const monitor = this._display.get_monitor_at_point(x, y); const scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * dx, scale * dy, 'rel'); } catch (e) { logError(e); } } pressPointer(button) { try { const [, x, y] = this._pointer.get_position(); const monitor = this._display.get_monitor_at_point(x, y); const scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}p`); } catch (e) { logError(e); } } releasePointer(button) { try { const [, x, y] = this._pointer.get_position(); const monitor = this._display.get_monitor_at_point(x, y); const scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}r`); } catch (e) { logError(e); } } scrollPointer(dx, dy) { if (dy > 0) this.clickPointer(4); else if (dy < 0) this.clickPointer(5); } /* * Phony virtual keyboard helpers */ _modeLock(keycode) { Atspi.generate_keyboard_event( keycode, null, Atspi.KeySynthType.PRESS ); } _modeUnlock(keycode) { Atspi.generate_keyboard_event( keycode, null, Atspi.KeySynthType.RELEASE ); } /* * Simulate a printable-ASCII character. * */ _pressASCII(key, modifiers) { try { // Press Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this._modeLock(XKeycode.Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this._modeLock(XKeycode.Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this._modeLock(XKeycode.Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this._modeLock(XKeycode.Super_L); Atspi.generate_keyboard_event( 0, key, Atspi.KeySynthType.STRING ); // Release Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this._modeUnlock(XKeycode.Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this._modeUnlock(XKeycode.Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this._modeUnlock(XKeycode.Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this._modeUnlock(XKeycode.Super_L); } catch (e) { logError(e); } } _pressKeysym(keysym, modifiers) { try { // Press Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this._modeLock(XKeycode.Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this._modeLock(XKeycode.Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this._modeLock(XKeycode.Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this._modeLock(XKeycode.Super_L); Atspi.generate_keyboard_event( keysym, null, Atspi.KeySynthType.PRESSRELEASE | Atspi.KeySynthType.SYM ); // Release Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this._modeUnlock(XKeycode.Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this._modeUnlock(XKeycode.Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this._modeUnlock(XKeycode.Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this._modeUnlock(XKeycode.Super_L); } catch (e) { logError(e); } } /** * Simulate the composition of a unicode character with: * Control+Shift+u, [hex], Return * * @param {number} key - An XKeycode * @param {number} modifiers - A modifier mask */ _pressUnicode(key, modifiers) { try { if (modifiers > 0) log('GSConnect: ignoring modifiers for unicode keyboard event'); // TODO: Using Control and Shift keysym is not working (it triggers // key release). Probably using LOCKMODIFIERS will not work either // as unlocking the modifier will not trigger a release // Activate compose sequence this._modeLock(XKeycode.Control_L); this._modeLock(XKeycode.Shift_L); this.pressreleaseKeysym(Gdk.KEY_U); this._modeUnlock(XKeycode.Control_L); this._modeUnlock(XKeycode.Shift_L); // Enter the unicode sequence const ucode = key.charCodeAt(0).toString(16); let keysym; for (let h = 0, len = ucode.length; h < len; h++) { keysym = Gdk.unicode_to_keyval(ucode.charAt(h).codePointAt(0)); this.pressreleaseKeysym(keysym); } // Finish the compose sequence this.pressreleaseKeysym(Gdk.KEY_Return); } catch (e) { logError(e); } } /* * Keyboard Events */ pressKeysym(keysym) { Atspi.generate_keyboard_event( keysym, null, Atspi.KeySynthType.PRESS | Atspi.KeySynthType.SYM ); } releaseKeysym(keysym) { Atspi.generate_keyboard_event( keysym, null, Atspi.KeySynthType.RELEASE | Atspi.KeySynthType.SYM ); } pressreleaseKeysym(keysym) { Atspi.generate_keyboard_event( keysym, null, Atspi.KeySynthType.PRESSRELEASE | Atspi.KeySynthType.SYM ); } pressKey(input, modifiers) { // We were passed a keysym if (typeof input === 'number') this._pressKeysym(input, modifiers); // Regular ASCII else if (_ASCII.test(input)) this._pressASCII(input, modifiers); // Unicode else this._pressUnicode(input, modifiers); } destroy() { try { Atspi.exit(); } catch (e) { // Silence errors } } } GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/clipboard.js000066400000000000000000000146171460766671100307660ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import GLib from 'gi://GLib'; import Gtk from 'gi://Gtk'; import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; const DBUS_NAME = 'org.gnome.Shell.Extensions.GSConnect.Clipboard'; const DBUS_PATH = '/org/gnome/Shell/Extensions/GSConnect/Clipboard'; /** * The service class for this component */ const Clipboard = GObject.registerClass({ GTypeName: 'GSConnectClipboard', Properties: { 'text': GObject.ParamSpec.string( 'text', 'Text Content', 'The current text content of the clipboard', GObject.ParamFlags.READWRITE, '' ), }, }, class Clipboard extends GObject.Object { _init() { super._init(); this._cancellable = new Gio.Cancellable(); this._clipboard = null; this._ownerChangeId = 0; this._nameWatcherId = Gio.bus_watch_name( Gio.BusType.SESSION, DBUS_NAME, Gio.BusNameWatcherFlags.NONE, this._onNameAppeared.bind(this), this._onNameVanished.bind(this) ); } get text() { if (this._text === undefined) this._text = ''; return this._text; } set text(content) { if (this.text === content) return; this._text = content; this.notify('text'); if (typeof content !== 'string') return; if (this._clipboard instanceof Gtk.Clipboard) this._clipboard.set_text(content, -1); if (this._clipboard instanceof Gio.DBusProxy) { this._clipboard.call('SetText', new GLib.Variant('(s)', [content]), Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable) .catch(debug); } } async _onNameAppeared(connection, name, name_owner) { try { // Cleanup the GtkClipboard if (this._clipboard && this._ownerChangeId > 0) { this._clipboard.disconnect(this._ownerChangeId); this._ownerChangeId = 0; } // Create a proxy for the remote clipboard this._clipboard = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SESSION, g_name: DBUS_NAME, g_object_path: DBUS_PATH, g_interface_name: DBUS_NAME, g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, }); await this._clipboard.init_async(GLib.PRIORITY_DEFAULT, this._cancellable); this._ownerChangeId = this._clipboard.connect('g-signal', this._onOwnerChange.bind(this)); this._onOwnerChange(); if (!globalThis.HAVE_GNOME) { // Directly subscrible signal this.signalHandler = Gio.DBus.session.signal_subscribe( DBUS_NAME, DBUS_NAME, 'OwnerChange', DBUS_PATH, null, Gio.DBusSignalFlags.NONE, this._onOwnerChange.bind(this) ); } } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { debug(e); this._onNameVanished(null, null); } } } _onNameVanished(connection, name) { if (this._clipboard && this._ownerChangeId > 0) { this._clipboard.disconnect(this._ownerChangeId); this._clipboardChangedId = 0; } const display = Gdk.Display.get_default(); this._clipboard = Gtk.Clipboard.get_default(display); this._ownerChangeId = this._clipboard.connect('owner-change', this._onOwnerChange.bind(this)); this._onOwnerChange(); } async _onOwnerChange() { try { if (this._clipboard instanceof Gtk.Clipboard) await this._gtkUpdateText(); else if (this._clipboard instanceof Gio.DBusProxy) await this._proxyUpdateText(); } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) debug(e); } } _applyUpdate(text) { if (typeof text !== 'string' || this.text === text) return; this._text = text; this.notify('text'); } /* * Proxy Clipboard */ async _proxyUpdateText() { let reply = await this._clipboard.call('GetMimetypes', null, Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable); const mimetypes = reply.deepUnpack()[0]; // Special case for a cleared clipboard if (mimetypes.length === 0) return this._applyUpdate(''); // Special case to ignore copied files if (mimetypes.includes('text/uri-list')) return; reply = await this._clipboard.call('GetText', null, Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable); const text = reply.deepUnpack()[0]; this._applyUpdate(text); } /* * GtkClipboard */ async _gtkUpdateText() { const mimetypes = await new Promise((resolve, reject) => { this._clipboard.request_targets((clipboard, atoms) => resolve(atoms)); }); // Special case for a cleared clipboard if (mimetypes.length === 0) return this._applyUpdate(''); // Special case to ignore copied files if (mimetypes.includes('text/uri-list')) return; const text = await new Promise((resolve, reject) => { this._clipboard.request_text((clipboard, text) => resolve(text)); }); this._applyUpdate(text); } destroy() { if (this._cancellable.is_cancelled()) return; this._cancellable.cancel(); if (this._clipboard && this._ownerChangeId > 0) { this._clipboard.disconnect(this._ownerChangeId); this._ownerChangedId = 0; } if (this._nameWatcherId > 0) { Gio.bus_unwatch_name(this._nameWatcherId); this._nameWatcherId = 0; } if (!globalThis.HAVE_GNOME && this.signalHandler) Gio.DBus.session.signal_unsubscribe(this.signalHandler); } }); export default Clipboard; // vim:tabstop=2:shiftwidth=2:expandtab GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/contacts.js000066400000000000000000000430771460766671100306470ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Config from '../../config.js'; let HAVE_EDS = true; let EBook = null; let EBookContacts = null; let EDataServer = null; try { EBook = (await import('gi://EBook')).default; EBookContacts = (await import('gi://EBookContacts')).default; EDataServer = (await import('gi://EDataServer')).default; } catch (e) { HAVE_EDS = false; } /** * A store for contacts */ const Store = GObject.registerClass({ GTypeName: 'GSConnectContactsStore', Properties: { 'context': GObject.ParamSpec.string( 'context', 'Context', 'Used as the cache directory, relative to Config.CACHEDIR', GObject.ParamFlags.CONSTRUCT_ONLY | GObject.ParamFlags.READWRITE, null ), }, Signals: { 'contact-added': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [GObject.TYPE_STRING], }, 'contact-removed': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [GObject.TYPE_STRING], }, 'contact-changed': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [GObject.TYPE_STRING], }, }, }, class Store extends GObject.Object { _init(context = null) { super._init({ context: context, }); this._cacheData = {}; this._edsPrepared = false; } /** * Parse an EContact and add it to the store. * * @param {EBookContacts.Contact} econtact - an EContact to parse * @param {string} [origin] - an optional origin string */ async _parseEContact(econtact, origin = 'desktop') { try { const contact = { id: econtact.id, name: _('Unknown Contact'), numbers: [], origin: origin, timestamp: 0, }; // Try to get a contact name if (econtact.full_name) contact.name = econtact.full_name; // Parse phone numbers const nums = econtact.get_attributes(EBookContacts.ContactField.TEL); for (const attr of nums) { const number = { value: attr.get_value(), type: 'unknown', }; if (attr.has_type('CELL')) number.type = 'cell'; else if (attr.has_type('HOME')) number.type = 'home'; else if (attr.has_type('WORK')) number.type = 'work'; contact.numbers.push(number); } // Try and get a contact photo const photo = econtact.photo; if (photo) { if (photo.type === EBookContacts.ContactPhotoType.INLINED) { const data = photo.get_inlined()[0]; contact.avatar = await this.storeAvatar(data); } else if (photo.type === EBookContacts.ContactPhotoType.URI) { const uri = econtact.photo.get_uri(); contact.avatar = uri.replace('file://', ''); } } this.add(contact, false); } catch (e) { logError(e, `Failed to parse VCard contact ${econtact.id}`); } } /* * AddressBook DBus callbacks */ _onObjectsAdded(connection, sender, path, iface, signal, params) { try { const adds = params.get_child_value(0).get_strv(); // NOTE: sequential pairs of vcard, id for (let i = 0, len = adds.length; i < len; i += 2) { try { const vcard = adds[i]; const econtact = EBookContacts.Contact.new_from_vcard(vcard); this._parseEContact(econtact); } catch (e) { debug(e); } } } catch (e) { debug(e); } } _onObjectsRemoved(connection, sender, path, iface, signal, params) { try { const changes = params.get_child_value(0).get_strv(); for (const id of changes) { try { this.remove(id, false); } catch (e) { debug(e); } } } catch (e) { debug(e); } } _onObjectsModified(connection, sender, path, iface, signal, params) { try { const changes = params.get_child_value(0).get_strv(); // NOTE: sequential pairs of vcard, id for (let i = 0, len = changes.length; i < len; i += 2) { try { const vcard = changes[i]; const econtact = EBookContacts.Contact.new_from_vcard(vcard); this._parseEContact(econtact); } catch (e) { debug(e); } } } catch (e) { debug(e); } } /* * SourceRegistryWatcher callbacks */ async _onAppeared(watcher, source) { try { // Get an EBookClient and EBookView const uid = source.get_uid(); const client = await EBook.BookClient.connect(source, null); const [view] = await client.get_view('exists "tel"', null); // Watch the view for changes to the address book const connection = view.get_connection(); const objectPath = view.get_object_path(); view._objectsAddedId = connection.signal_subscribe( null, 'org.gnome.evolution.dataserver.AddressBookView', 'ObjectsAdded', objectPath, null, Gio.DBusSignalFlags.NONE, this._onObjectsAdded.bind(this) ); view._objectsRemovedId = connection.signal_subscribe( null, 'org.gnome.evolution.dataserver.AddressBookView', 'ObjectsRemoved', objectPath, null, Gio.DBusSignalFlags.NONE, this._onObjectsRemoved.bind(this) ); view._objectsModifiedId = connection.signal_subscribe( null, 'org.gnome.evolution.dataserver.AddressBookView', 'ObjectsModified', objectPath, null, Gio.DBusSignalFlags.NONE, this._onObjectsModified.bind(this) ); view.start(); // Store the EBook in a map this._ebooks.set(uid, { source: source, client: client, view: view, }); } catch (e) { debug(e); } } _onDisappeared(watcher, source) { try { const uid = source.get_uid(); const ebook = this._ebooks.get(uid); if (ebook === undefined) return; // Disconnect the EBookView if (ebook.view) { const connection = ebook.view.get_connection(); connection.signal_unsubscribe(ebook.view._objectsAddedId); connection.signal_unsubscribe(ebook.view._objectsRemovedId); connection.signal_unsubscribe(ebook.view._objectsModifiedId); ebook.view.stop(); } this._ebooks.delete(uid); } catch (e) { debug(e); } } async _initEvolutionDataServer() { try { if (this._edsPrepared) return; this._edsPrepared = true; this._ebooks = new Map(); // Get the current EBooks const registry = await this._getESourceRegistry(); for (const source of registry.list_sources('Address Book')) await this._onAppeared(null, source); // Watch for new and removed sources this._watcher = new EDataServer.SourceRegistryWatcher({ registry: registry, extension_name: 'Address Book', }); this._appearedId = this._watcher.connect( 'appeared', this._onAppeared.bind(this) ); this._disappearedId = this._watcher.connect( 'disappeared', this._onDisappeared.bind(this) ); } catch (e) { const service = Gio.Application.get_default(); if (service !== null) service.notify_error(e); else logError(e); } } *[Symbol.iterator]() { const contacts = Object.values(this._cacheData); for (let i = 0, len = contacts.length; i < len; i++) yield contacts[i]; } get contacts() { return Object.values(this._cacheData); } get context() { if (this._context === undefined) this._context = null; return this._context; } set context(context) { this._context = context; this._cacheDir = Gio.File.new_for_path(Config.CACHEDIR); if (context !== null) this._cacheDir = this._cacheDir.get_child(context); GLib.mkdir_with_parents(this._cacheDir.get_path(), 448); this._cacheFile = this._cacheDir.get_child('contacts.json'); } /** * Save a Uint8Array to file and return the path * * @param {Uint8Array} contents - An image byte array * @return {string|undefined} File path or %undefined on failure */ async storeAvatar(contents) { const md5 = GLib.compute_checksum_for_data(GLib.ChecksumType.MD5, contents); const file = this._cacheDir.get_child(`${md5}`); if (!file.query_exists(null)) { try { await file.replace_contents_bytes_async( new GLib.Bytes(contents), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); } catch (e) { debug(e, 'Storing avatar'); return undefined; } } return file.get_path(); } /** * Query the Store for a contact by name and/or number. * * @param {Object} query - A query object * @param {string} [query.name] - The contact's name * @param {string} query.number - The contact's number * @return {Object} A contact object */ query(query) { // First look for an existing contact by number const contacts = this.contacts; const matches = []; const qnumber = query.number.toPhoneNumber(); for (let i = 0, len = contacts.length; i < len; i++) { const contact = contacts[i]; for (const num of contact.numbers) { const cnumber = num.value.toPhoneNumber(); if (qnumber.endsWith(cnumber) || cnumber.endsWith(qnumber)) { // If no query name or exact match, return immediately if (!query.name || query.name === contact.name) return contact; // Otherwise we might find an exact name match that shares // the number with another contact matches.push(contact); } } } // Return the first match (pretty much what Android does) if (matches.length > 0) return matches[0]; // No match; return a mock contact with a unique ID let id = GLib.uuid_string_random(); while (this._cacheData.hasOwnProperty(id)) id = GLib.uuid_string_random(); return { id: id, name: query.name || query.number, numbers: [{value: query.number, type: 'unknown'}], origin: 'gsconnect', }; } get_contact(position) { if (this._cacheData[position] !== undefined) return this._cacheData[position]; return null; } /** * Add a contact, checking for validity * * @param {Object} contact - A contact object * @param {boolean} write - Write to disk */ add(contact, write = true) { // Ensure the contact has a unique id if (!contact.id) { let id = GLib.uuid_string_random(); while (this._cacheData[id]) id = GLib.uuid_string_random(); contact.id = id; } // Ensure the contact has an origin if (!contact.origin) contact.origin = 'gsconnect'; // This is an updated contact if (this._cacheData[contact.id]) { this._cacheData[contact.id] = contact; this.emit('contact-changed', contact.id); // This is a new contact } else { this._cacheData[contact.id] = contact; this.emit('contact-added', contact.id); } // Write if requested if (write) this.save(); } /** * Remove a contact by id * * @param {string} id - The id of the contact to delete * @param {boolean} write - Write to disk */ remove(id, write = true) { // Only remove if the contact actually exists if (this._cacheData[id]) { delete this._cacheData[id]; this.emit('contact-removed', id); // Write if requested if (write) this.save(); } } /** * Lookup a contact for each address object in @addresses and return a * dictionary of address (eg. phone number) to contact object. * * { "555-5555": { "name": "...", "numbers": [], ... } } * * @param {Object[]} addresses - A list of address objects * @return {Object} A dictionary of phone numbers and contacts */ lookupAddresses(addresses) { const contacts = {}; // Lookup contacts for each address for (let i = 0, len = addresses.length; i < len; i++) { const address = addresses[i].address; contacts[address] = this.query({ number: address, }); } return contacts; } async clear() { try { const contacts = this.contacts; for (let i = 0, len = contacts.length; i < len; i++) await this.remove(contacts[i].id, false); await this.save(); } catch (e) { debug(e); } } /** * Update the contact store from a dictionary of our custom contact objects. * * @param {Object} json - an Object of contact Objects */ async update(json = {}) { try { let contacts = Object.values(json); for (let i = 0, len = contacts.length; i < len; i++) { const new_contact = contacts[i]; const contact = this._cacheData[new_contact.id]; if (!contact || new_contact.timestamp !== contact.timestamp) await this.add(new_contact, false); } // Prune contacts contacts = this.contacts; for (let i = 0, len = contacts.length; i < len; i++) { const contact = contacts[i]; if (!json[contact.id]) await this.remove(contact.id, false); } await this.save(); } catch (e) { debug(e, 'Updating contacts'); } } /** * Fetch and update the contact store from its source. * * The default function initializes the EDS server, or logs a debug message * if EDS is unavailable. Derived classes should request an update from the * remote source. */ async fetch() { try { if (this.context === null && HAVE_EDS) await this._initEvolutionDataServer(); else throw new Error('Evolution Data Server not available'); } catch (e) { debug(e); } } /** * Load the contacts from disk. */ async load() { try { const [contents] = await this._cacheFile.load_contents_async(null); this._cacheData = JSON.parse(new TextDecoder().decode(contents)); } catch (e) { debug(e); } finally { this.notify('context'); } } /** * Save the contacts to disk. */ async save() { // EDS is handling storage if (this.context === null && HAVE_EDS) return; if (this.__cache_lock) { this.__cache_queue = true; return; } try { this.__cache_lock = true; const contents = new GLib.Bytes(JSON.stringify(this._cacheData, null, 2)); await this._cacheFile.replace_contents_bytes_async(contents, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); } catch (e) { debug(e); } finally { this.__cache_lock = false; if (this.__cache_queue) { this.__cache_queue = false; this.save(); } } } destroy() { if (this._watcher !== undefined) { this._watcher.disconnect(this._appearedId); this._watcher.disconnect(this._disappearedId); this._watcher = undefined; for (const ebook of this._ebooks.values()) this._onDisappeared(null, ebook.source); this._edsPrepared = false; } } }); export default Store; GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/index.js000066400000000000000000000043121460766671100301250ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as atspi from './atspi.js'; import * as clipboard from './clipboard.js'; import * as contacts from './contacts.js'; import * as input from './input.js'; import * as mpris from './mpris.js'; import * as notification from './notification.js'; import * as pulseaudio from './pulseaudio.js'; import * as session from './session.js'; import * as sound from './sound.js'; import * as upower from './upower.js'; import * as ydotool from './ydotool.js'; export const functionOverrides = {}; const components = { atspi, clipboard, contacts, input, mpris, notification, pulseaudio, session, sound, upower, ydotool, }; /* * Singleton Tracker */ const Default = new Map(); /** * Acquire a reference to a component. Calls to this function should always be * followed by a call to `release()`. * * @param {string} name - The module name * @return {*} The default instance of a component */ export function acquire(name) { if (functionOverrides.acquire) return functionOverrides.acquire(name); let component; try { let info = Default.get(name); if (info === undefined) { const module = components[name]; info = { instance: new module.default(), refcount: 0, }; Default.set(name, info); } info.refcount++; component = info.instance; } catch (e) { debug(e, name); } return component; } /** * Release a reference on a component. If the caller was the last reference * holder, the component will be freed. * * @param {string} name - The module name * @return {null} A %null value, useful for overriding a traced variable */ export function release(name) { if (functionOverrides.release) return functionOverrides.release(name); try { const info = Default.get(name); if (info.refcount === 1) { info.instance.destroy(); Default.delete(name); } info.refcount--; } catch (e) { debug(e, name); } return null; } GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/input.js000066400000000000000000000321541460766671100301620ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import AtspiController from './atspi.js'; const SESSION_TIMEOUT = 15; const RemoteSession = GObject.registerClass({ GTypeName: 'GSConnectRemoteSession', Implements: [Gio.DBusInterface], Signals: { 'closed': { flags: GObject.SignalFlags.RUN_FIRST, }, }, }, class RemoteSession extends Gio.DBusProxy { _init(objectPath) { super._init({ g_bus_type: Gio.BusType.SESSION, g_name: 'org.gnome.Mutter.RemoteDesktop', g_object_path: objectPath, g_interface_name: 'org.gnome.Mutter.RemoteDesktop.Session', g_flags: Gio.DBusProxyFlags.NONE, }); this._started = false; } vfunc_g_signal(sender_name, signal_name, parameters) { if (signal_name === 'Closed') this.emit('closed'); } _call(name, parameters = null) { if (!this._started) return; // Pass a null callback to allow this call to finish itself this.call(name, parameters, Gio.DBusCallFlags.NONE, -1, null, null); } get session_id() { try { return this.get_cached_property('SessionId').unpack(); } catch (e) { return null; } } async start() { try { if (this._started) return; // Initialize the proxy, and start the session await this.init_async(GLib.PRIORITY_DEFAULT, null); await this.call('Start', null, Gio.DBusCallFlags.NONE, -1, null); this._started = true; } catch (e) { this.destroy(); Gio.DBusError.strip_remote_error(e); throw e; } } stop() { if (this._started) { this._started = false; // Pass a null callback to allow this call to finish itself this.call('Stop', null, Gio.DBusCallFlags.NONE, -1, null, null); } } _translateButton(button) { switch (button) { case Gdk.BUTTON_PRIMARY: return 0x110; case Gdk.BUTTON_MIDDLE: return 0x112; case Gdk.BUTTON_SECONDARY: return 0x111; case 4: return 0; // FIXME case 5: return 0x10F; // up } } movePointer(dx, dy) { this._call( 'NotifyPointerMotionRelative', GLib.Variant.new('(dd)', [dx, dy]) ); } pressPointer(button) { button = this._translateButton(button); this._call( 'NotifyPointerButton', GLib.Variant.new('(ib)', [button, true]) ); } releasePointer(button) { button = this._translateButton(button); this._call( 'NotifyPointerButton', GLib.Variant.new('(ib)', [button, false]) ); } clickPointer(button) { button = this._translateButton(button); this._call( 'NotifyPointerButton', GLib.Variant.new('(ib)', [button, true]) ); this._call( 'NotifyPointerButton', GLib.Variant.new('(ib)', [button, false]) ); } doubleclickPointer(button) { this.clickPointer(button); this.clickPointer(button); } scrollPointer(dx, dy) { if (dy > 0) { this._call( 'NotifyPointerAxisDiscrete', GLib.Variant.new('(ui)', [Gdk.ScrollDirection.UP, 1]) ); } else if (dy < 0) { this._call( 'NotifyPointerAxisDiscrete', GLib.Variant.new('(ui)', [Gdk.ScrollDirection.UP, -1]) ); } } /* * Keyboard Events */ pressKeysym(keysym) { this._call( 'NotifyKeyboardKeysym', GLib.Variant.new('(ub)', [keysym, true]) ); } releaseKeysym(keysym) { this._call( 'NotifyKeyboardKeysym', GLib.Variant.new('(ub)', [keysym, false]) ); } pressreleaseKeysym(keysym) { this._call( 'NotifyKeyboardKeysym', GLib.Variant.new('(ub)', [keysym, true]) ); this._call( 'NotifyKeyboardKeysym', GLib.Variant.new('(ub)', [keysym, false]) ); } /* * High-level keyboard input */ pressKey(input, modifiers) { // Press Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this.pressKeysym(Gdk.KEY_Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this.pressKeysym(Gdk.KEY_Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this.pressKeysym(Gdk.KEY_Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this.pressKeysym(Gdk.KEY_Super_L); if (typeof input === 'string') { const keysym = Gdk.unicode_to_keyval(input.codePointAt(0)); this.pressreleaseKeysym(keysym); } else { this.pressreleaseKeysym(input); } // Release Modifiers if (modifiers & Gdk.ModifierType.MOD1_MASK) this.releaseKeysym(Gdk.KEY_Alt_L); if (modifiers & Gdk.ModifierType.CONTROL_MASK) this.releaseKeysym(Gdk.KEY_Control_L); if (modifiers & Gdk.ModifierType.SHIFT_MASK) this.releaseKeysym(Gdk.KEY_Shift_L); if (modifiers & Gdk.ModifierType.SUPER_MASK) this.releaseKeysym(Gdk.KEY_Super_L); } destroy() { if (this.__disposed === undefined) { this.__disposed = true; GObject.signal_handlers_destroy(this); } } }); export default class Controller { constructor() { this._nameAppearedId = 0; this._session = null; this._sessionCloseId = 0; this._sessionExpiry = 0; this._sessionExpiryId = 0; this._sessionStarting = false; // Watch for the RemoteDesktop portal this._nameWatcherId = Gio.bus_watch_name( Gio.BusType.SESSION, 'org.gnome.Mutter.RemoteDesktop', Gio.BusNameWatcherFlags.NONE, this._onNameAppeared.bind(this), this._onNameVanished.bind(this) ); } get connection() { if (this._connection === undefined) this._connection = null; return this._connection; } _onNameAppeared(connection, name, name_owner) { try { this._connection = connection; } catch (e) { logError(e); } } _onNameVanished(connection, name) { try { if (this._session !== null) this._onSessionClosed(this._session); } catch (e) { logError(e); } } _onSessionClosed(session) { // Disconnect from the session if (this._sessionClosedId > 0) { session.disconnect(this._sessionClosedId); this._sessionClosedId = 0; } // Destroy the session session.destroy(); this._session = null; } _onSessionExpired() { // If the session has been used recently, schedule a new expiry const remainder = Math.floor(this._sessionExpiry - (Date.now() / 1000)); if (remainder > 0) { this._sessionExpiryId = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT, remainder, this._onSessionExpired.bind(this) ); return GLib.SOURCE_REMOVE; } // Otherwise if there's an active session, close it if (this._session !== null) this._session.stop(); // Reset the GSource Id this._sessionExpiryId = 0; return GLib.SOURCE_REMOVE; } async _createRemoteDesktopSession() { if (this.connection === null) return Promise.reject(new Error('No DBus connection')); const reply = await this.connection.call( 'org.gnome.Mutter.RemoteDesktop', '/org/gnome/Mutter/RemoteDesktop', 'org.gnome.Mutter.RemoteDesktop', 'CreateSession', null, null, Gio.DBusCallFlags.NONE, -1, null); return reply.deepUnpack()[0]; } async _ensureAdapter() { try { // Update the timestamp of the last event this._sessionExpiry = Math.floor((Date.now() / 1000) + SESSION_TIMEOUT); // Session is active if (this._session !== null) return; // Mutter's RemoteDesktop is not available, fall back to Atspi if (this.connection === null) { debug('Falling back to Atspi'); this._session = new AtspiController(); // Mutter is available and there isn't another session starting } else if (this._sessionStarting === false) { this._sessionStarting = true; debug('Creating Mutter RemoteDesktop session'); // This takes three steps: creating the remote desktop session, // starting the session, and creating a screencast session for // the remote desktop session. const objectPath = await this._createRemoteDesktopSession(); this._session = new RemoteSession(objectPath); await this._session.start(); // Watch for the session ending this._sessionClosedId = this._session.connect( 'closed', this._onSessionClosed.bind(this) ); if (this._sessionExpiryId === 0) { this._sessionExpiryId = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT, SESSION_TIMEOUT, this._onSessionExpired.bind(this) ); } this._sessionStarting = false; } } catch (e) { logError(e); if (this._session !== null) { this._session.destroy(); this._session = null; } this._sessionStarting = false; } } /* * Pointer Events */ movePointer(dx, dy) { try { if (dx === 0 && dy === 0) return; this._ensureAdapter(); this._session.movePointer(dx, dy); } catch (e) { debug(e); } } pressPointer(button) { try { this._ensureAdapter(); this._session.pressPointer(button); } catch (e) { debug(e); } } releasePointer(button) { try { this._ensureAdapter(); this._session.releasePointer(button); } catch (e) { debug(e); } } clickPointer(button) { try { this._ensureAdapter(); this._session.clickPointer(button); } catch (e) { debug(e); } } doubleclickPointer(button) { try { this._ensureAdapter(); this._session.doubleclickPointer(button); } catch (e) { debug(e); } } scrollPointer(dx, dy) { if (dx === 0 && dy === 0) return; try { this._ensureAdapter(); this._session.scrollPointer(dx, dy); } catch (e) { debug(e); } } /* * Keyboard Events */ pressKeysym(keysym) { try { this._ensureAdapter(); this._session.pressKeysym(keysym); } catch (e) { debug(e); } } releaseKeysym(keysym) { try { this._ensureAdapter(); this._session.releaseKeysym(keysym); } catch (e) { debug(e); } } pressreleaseKeysym(keysym) { try { this._ensureAdapter(); this._session.pressreleaseKeysym(keysym); } catch (e) { debug(e); } } /* * High-level keyboard input */ pressKeys(input, modifiers) { try { this._ensureAdapter(); if (typeof input === 'string') { for (let i = 0; i < input.length; i++) this._session.pressKey(input[i], modifiers); } else { this._session.pressKey(input, modifiers); } } catch (e) { debug(e); } } destroy() { if (this._session !== null) { // Disconnect from the session if (this._sessionClosedId > 0) { this._session.disconnect(this._sessionClosedId); this._sessionClosedId = 0; } this._session.destroy(); this._session = null; } if (this._nameWatcherId > 0) { Gio.bus_unwatch_name(this._nameWatcherId); this._nameWatcherId = 0; } } } GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/mpris.js000066400000000000000000000620051460766671100301530ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; export const Player = GObject.registerClass({ GTypeName: 'GSConnectMediaPlayerInterface', Properties: { // Application Properties 'CanQuit': GObject.ParamSpec.boolean( 'CanQuit', 'Can Quit', 'Whether the client can call the Quit method.', GObject.ParamFlags.READABLE, false ), 'Fullscreen': GObject.ParamSpec.boolean( 'Fullscreen', 'Fullscreen', 'Whether the player is in fullscreen mode.', GObject.ParamFlags.READWRITE, false ), 'CanSetFullscreen': GObject.ParamSpec.boolean( 'CanSetFullscreen', 'Can Set Fullscreen', 'Whether the client can set the Fullscreen property.', GObject.ParamFlags.READABLE, false ), 'CanRaise': GObject.ParamSpec.boolean( 'CanRaise', 'Can Raise', 'Whether the client can call the Raise method.', GObject.ParamFlags.READABLE, false ), 'HasTrackList': GObject.ParamSpec.boolean( 'HasTrackList', 'Has Track List', 'Whether the player has a track list.', GObject.ParamFlags.READABLE, false ), 'Identity': GObject.ParamSpec.string( 'Identity', 'Identity', 'The application name.', GObject.ParamFlags.READABLE, null ), 'DesktopEntry': GObject.ParamSpec.string( 'DesktopEntry', 'DesktopEntry', 'The basename of an installed .desktop file.', GObject.ParamFlags.READABLE, null ), 'SupportedUriSchemes': GObject.param_spec_variant( 'SupportedUriSchemes', 'Supported URI Schemes', 'The URI schemes supported by the media player.', new GLib.VariantType('as'), null, GObject.ParamFlags.READABLE ), 'SupportedMimeTypes': GObject.param_spec_variant( 'SupportedMimeTypes', 'Supported MIME Types', 'The mime-types supported by the media player.', new GLib.VariantType('as'), null, GObject.ParamFlags.READABLE ), // Player Properties 'PlaybackStatus': GObject.ParamSpec.string( 'PlaybackStatus', 'Playback Status', 'The current playback status.', GObject.ParamFlags.READABLE, null ), 'LoopStatus': GObject.ParamSpec.string( 'LoopStatus', 'Loop Status', 'The current loop status.', GObject.ParamFlags.READWRITE, null ), 'Rate': GObject.ParamSpec.double( 'Rate', 'Rate', 'The current playback rate.', GObject.ParamFlags.READWRITE, 0.0, 1.0, 1.0 ), 'MinimumRate': GObject.ParamSpec.double( 'MinimumRate', 'Minimum Rate', 'The minimum playback rate.', GObject.ParamFlags.READWRITE, 0.0, 1.0, 1.0 ), 'MaximimRate': GObject.ParamSpec.double( 'MaximumRate', 'Maximum Rate', 'The maximum playback rate.', GObject.ParamFlags.READWRITE, 0.0, 1.0, 1.0 ), 'Shuffle': GObject.ParamSpec.boolean( 'Shuffle', 'Shuffle', 'Whether track changes are linear.', GObject.ParamFlags.READWRITE, null ), 'Metadata': GObject.param_spec_variant( 'Metadata', 'Metadata', 'The metadata of the current element.', new GLib.VariantType('a{sv}'), null, GObject.ParamFlags.READABLE ), 'Volume': GObject.ParamSpec.double( 'Volume', 'Volume', 'The volume level.', GObject.ParamFlags.READWRITE, 0.0, 1.0, 1.0 ), 'Position': GObject.ParamSpec.int64( 'Position', 'Position', 'The current track position in microseconds.', GObject.ParamFlags.READABLE, 0, Number.MAX_SAFE_INTEGER, 0 ), 'CanGoNext': GObject.ParamSpec.boolean( 'CanGoNext', 'Can Go Next', 'Whether the client can call the Next method.', GObject.ParamFlags.READABLE, false ), 'CanGoPrevious': GObject.ParamSpec.boolean( 'CanGoPrevious', 'Can Go Previous', 'Whether the client can call the Previous method.', GObject.ParamFlags.READABLE, false ), 'CanPlay': GObject.ParamSpec.boolean( 'CanPlay', 'Can Play', 'Whether playback can be started using Play or PlayPause.', GObject.ParamFlags.READABLE, false ), 'CanPause': GObject.ParamSpec.boolean( 'CanPause', 'Can Pause', 'Whether playback can be paused using Play or PlayPause.', GObject.ParamFlags.READABLE, false ), 'CanSeek': GObject.ParamSpec.boolean( 'CanSeek', 'Can Seek', 'Whether the client can control the playback position using Seek and SetPosition.', GObject.ParamFlags.READABLE, false ), 'CanControl': GObject.ParamSpec.boolean( 'CanControl', 'Can Control', 'Whether the media player may be controlled over this interface.', GObject.ParamFlags.READABLE, false ), }, Signals: { 'Seeked': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [GObject.TYPE_INT64], }, }, }, class Player extends GObject.Object { /* * The org.mpris.MediaPlayer2 Interface */ get CanQuit() { if (this._CanQuit === undefined) this._CanQuit = false; return this._CanQuit; } get CanRaise() { if (this._CanRaise === undefined) this._CanRaise = false; return this._CanRaise; } get CanSetFullscreen() { if (this._CanFullscreen === undefined) this._CanFullscreen = false; return this._CanFullscreen; } get DesktopEntry() { if (this._DesktopEntry === undefined) return 'org.gnome.Shell.Extensions.GSConnect'; return this._DesktopEntry; } get Fullscreen() { if (this._Fullscreen === undefined) this._Fullscreen = false; return this._Fullscreen; } set Fullscreen(mode) { if (this.Fullscreen === mode) return; this._Fullscreen = mode; this.notify('Fullscreen'); } get HasTrackList() { if (this._HasTrackList === undefined) this._HasTrackList = false; return this._HasTrackList; } get Identity() { if (this._Identity === undefined) this._Identity = ''; return this._Identity; } get SupportedMimeTypes() { if (this._SupportedMimeTypes === undefined) this._SupportedMimeTypes = []; return this._SupportedMimeTypes; } get SupportedUriSchemes() { if (this._SupportedUriSchemes === undefined) this._SupportedUriSchemes = []; return this._SupportedUriSchemes; } Quit() { throw new GObject.NotImplementedError(); } Raise() { throw new GObject.NotImplementedError(); } /* * The org.mpris.MediaPlayer2.Player Interface */ get CanControl() { if (this._CanControl === undefined) this._CanControl = false; return this._CanControl; } get CanGoNext() { if (this._CanGoNext === undefined) this._CanGoNext = false; return this._CanGoNext; } get CanGoPrevious() { if (this._CanGoPrevious === undefined) this._CanGoPrevious = false; return this._CanGoPrevious; } get CanPause() { if (this._CanPause === undefined) this._CanPause = false; return this._CanPause; } get CanPlay() { if (this._CanPlay === undefined) this._CanPlay = false; return this._CanPlay; } get CanSeek() { if (this._CanSeek === undefined) this._CanSeek = false; return this._CanSeek; } get LoopStatus() { if (this._LoopStatus === undefined) this._LoopStatus = 'None'; return this._LoopStatus; } set LoopStatus(status) { if (this.LoopStatus === status) return; this._LoopStatus = status; this.notify('LoopStatus'); } get MaximumRate() { if (this._MaximumRate === undefined) this._MaximumRate = 1.0; return this._MaximumRate; } get Metadata() { if (this._Metadata === undefined) { this._Metadata = { 'xesam:artist': [_('Unknown')], 'xesam:album': _('Unknown'), 'xesam:title': _('Unknown'), 'mpris:length': 0, }; } return this._Metadata; } get MinimumRate() { if (this._MinimumRate === undefined) this._MinimumRate = 1.0; return this._MinimumRate; } get PlaybackStatus() { if (this._PlaybackStatus === undefined) this._PlaybackStatus = 'Stopped'; return this._PlaybackStatus; } get Position() { if (this._Position === undefined) this._Position = 0; return this._Position; } get Rate() { if (this._Rate === undefined) this._Rate = 1.0; return this._Rate; } set Rate(rate) { if (this.Rate === rate) return; this._Rate = rate; this.notify('Rate'); } get Shuffle() { if (this._Shuffle === undefined) this._Shuffle = false; return this._Shuffle; } set Shuffle(mode) { if (this.Shuffle === mode) return; this._Shuffle = mode; this.notify('Shuffle'); } get Volume() { if (this._Volume === undefined) this._Volume = 1.0; return this._Volume; } set Volume(level) { if (this.Volume === level) return; this._Volume = level; this.notify('Volume'); } Next() { throw new GObject.NotImplementedError(); } OpenUri(uri) { throw new GObject.NotImplementedError(); } Previous() { throw new GObject.NotImplementedError(); } Pause() { throw new GObject.NotImplementedError(); } Play() { throw new GObject.NotImplementedError(); } PlayPause() { throw new GObject.NotImplementedError(); } Seek(offset) { throw new GObject.NotImplementedError(); } SetPosition(trackId, position) { throw new GObject.NotImplementedError(); } Stop() { throw new GObject.NotImplementedError(); } }); /** * An aggregate of the org.mpris.MediaPlayer2 and org.mpris.MediaPlayer2.Player * interfaces. */ const PlayerProxy = GObject.registerClass({ GTypeName: 'GSConnectMPRISPlayer', }, class PlayerProxy extends Player { _init(name) { super._init(); this._application = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SESSION, g_name: name, g_object_path: '/org/mpris/MediaPlayer2', g_interface_name: 'org.mpris.MediaPlayer2', }); this._applicationChangedId = this._application.connect( 'g-properties-changed', this._onPropertiesChanged.bind(this) ); this._player = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SESSION, g_name: name, g_object_path: '/org/mpris/MediaPlayer2', g_interface_name: 'org.mpris.MediaPlayer2.Player', }); this._playerChangedId = this._player.connect( 'g-properties-changed', this._onPropertiesChanged.bind(this) ); this._playerSignalId = this._player.connect( 'g-signal', this._onSignal.bind(this) ); this._cancellable = new Gio.Cancellable(); } _onSignal(proxy, sender_name, signal_name, parameters) { try { if (signal_name !== 'Seeked') return; this.emit('Seeked', parameters.deepUnpack()[0]); } catch (e) { debug(e, proxy.g_name); } } _call(proxy, name, parameters = null) { proxy.call( name, parameters, Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable, (proxy, result) => { try { proxy.call_finish(result); } catch (e) { Gio.DBusError.strip_remote_error(e); debug(e, proxy.g_name); } } ); } _get(proxy, name, fallback = null) { try { return proxy.get_cached_property(name).recursiveUnpack(); } catch (e) { return fallback; } } _set(proxy, name, value) { try { proxy.set_cached_property(name, value); proxy.call( 'org.freedesktop.DBus.Properties.Set', new GLib.Variant('(ssv)', [proxy.g_interface_name, name, value]), Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable, (proxy, result) => { try { proxy.call_finish(result); } catch (e) { Gio.DBusError.strip_remote_error(e); debug(e, proxy.g_name); } } ); } catch (e) { debug(e, proxy.g_name); } } _onPropertiesChanged(proxy, changed, invalidated) { try { this.freeze_notify(); for (const name in changed.deepUnpack()) this.notify(name); this.thaw_notify(); } catch (e) { debug(e, proxy.g_name); } } /* * The org.mpris.MediaPlayer2 Interface */ get CanQuit() { return this._get(this._application, 'CanQuit', false); } get CanRaise() { return this._get(this._application, 'CanRaise', false); } get CanSetFullscreen() { return this._get(this._application, 'CanSetFullscreen', false); } get DesktopEntry() { return this._get(this._application, 'DesktopEntry', null); } get Fullscreen() { return this._get(this._application, 'Fullscreen', false); } set Fullscreen(mode) { this._set(this._application, 'Fullscreen', new GLib.Variant('b', mode)); } get HasTrackList() { return this._get(this._application, 'HasTrackList', false); } get Identity() { return this._get(this._application, 'Identity', _('Unknown')); } get SupportedMimeTypes() { return this._get(this._application, 'SupportedMimeTypes', []); } get SupportedUriSchemes() { return this._get(this._application, 'SupportedUriSchemes', []); } Quit() { this._call(this._application, 'Quit'); } Raise() { this._call(this._application, 'Raise'); } /* * The org.mpris.MediaPlayer2.Player Interface */ get CanControl() { return this._get(this._player, 'CanControl', false); } get CanGoNext() { return this._get(this._player, 'CanGoNext', false); } get CanGoPrevious() { return this._get(this._player, 'CanGoPrevious', false); } get CanPause() { return this._get(this._player, 'CanPause', false); } get CanPlay() { return this._get(this._player, 'CanPlay', false); } get CanSeek() { return this._get(this._player, 'CanSeek', false); } get LoopStatus() { return this._get(this._player, 'LoopStatus', 'None'); } set LoopStatus(status) { this._set(this._player, 'LoopStatus', new GLib.Variant('s', status)); } get MaximumRate() { return this._get(this._player, 'MaximumRate', 1.0); } get Metadata() { if (this._metadata === undefined) { this._metadata = { 'xesam:artist': [_('Unknown')], 'xesam:album': _('Unknown'), 'xesam:title': _('Unknown'), 'mpris:length': 0, }; } return this._get(this._player, 'Metadata', this._metadata); } get MinimumRate() { return this._get(this._player, 'MinimumRate', 1.0); } get PlaybackStatus() { return this._get(this._player, 'PlaybackStatus', 'Stopped'); } // g-properties-changed is not emitted for this property get Position() { try { const reply = this._player.call_sync( 'org.freedesktop.DBus.Properties.Get', new GLib.Variant('(ss)', [ 'org.mpris.MediaPlayer2.Player', 'Position', ]), Gio.DBusCallFlags.NONE, -1, null ); return reply.recursiveUnpack()[0]; } catch (e) { return 0; } } get Rate() { return this._get(this._player, 'Rate', 1.0); } set Rate(rate) { this._set(this._player, 'Rate', new GLib.Variant('d', rate)); } get Shuffle() { return this._get(this._player, 'Shuffle', false); } set Shuffle(mode) { this._set(this._player, 'Shuffle', new GLib.Variant('b', mode)); } get Volume() { return this._get(this._player, 'Volume', 1.0); } set Volume(level) { this._set(this._player, 'Volume', new GLib.Variant('d', level)); } Next() { this._call(this._player, 'Next'); } OpenUri(uri) { this._call(this._player, 'OpenUri', new GLib.Variant('(s)', [uri])); } Previous() { this._call(this._player, 'Previous'); } Pause() { this._call(this._player, 'Pause'); } Play() { this._call(this._player, 'Play'); } PlayPause() { this._call(this._player, 'PlayPause'); } Seek(offset) { this._call(this._player, 'Seek', new GLib.Variant('(x)', [offset])); } SetPosition(trackId, position) { this._call(this._player, 'SetPosition', new GLib.Variant('(ox)', [trackId, position])); } Stop() { this._call(this._player, 'Stop'); } destroy() { if (this._cancellable.is_cancelled()) return; this._cancellable.cancel(); this._application.disconnect(this._applicationChangedId); this._player.disconnect(this._playerChangedId); this._player.disconnect(this._playerSignalId); } }); /** * A manager for media players */ const Manager = GObject.registerClass({ GTypeName: 'GSConnectMPRISManager', Signals: { 'player-added': { param_types: [GObject.TYPE_OBJECT], }, 'player-removed': { param_types: [GObject.TYPE_OBJECT], }, 'player-changed': { param_types: [GObject.TYPE_OBJECT], }, 'player-seeked': { param_types: [GObject.TYPE_OBJECT, GObject.TYPE_INT64], }, }, }, class Manager extends GObject.Object { _init() { super._init(); // Asynchronous setup this._cancellable = new Gio.Cancellable(); this._connection = Gio.DBus.session; this._players = new Map(); this._paused = new Map(); this._nameOwnerChangedId = Gio.DBus.session.signal_subscribe( 'org.freedesktop.DBus', 'org.freedesktop.DBus', 'NameOwnerChanged', '/org/freedesktop/DBus', 'org.mpris.MediaPlayer2', Gio.DBusSignalFlags.MATCH_ARG0_NAMESPACE, this._onNameOwnerChanged.bind(this) ); this._loadPlayers(); } async _loadPlayers() { try { const reply = await this._connection.call( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'ListNames', null, null, Gio.DBusCallFlags.NONE, -1, this._cancellable); const names = reply.deepUnpack()[0]; for (let i = 0, len = names.length; i < len; i++) { const name = names[i]; if (!name.startsWith('org.mpris.MediaPlayer2')) continue; if (!name.includes('GSConnect')) this._addPlayer(name); } } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) logError(e); } } _onNameOwnerChanged(connection, sender, object, iface, signal, parameters) { const [name, oldOwner, newOwner] = parameters.deepUnpack(); if (name.includes('GSConnect')) return; if (newOwner.length) this._addPlayer(name); else if (oldOwner.length) this._removePlayer(name); } async _addPlayer(name) { try { if (!this._players.has(name)) { const player = new PlayerProxy(name); await Promise.all([ player._application.init_async(GLib.PRIORITY_DEFAULT, this._cancellable), player._player.init_async(GLib.PRIORITY_DEFAULT, this._cancellable), ]); player.connect('notify', (player) => this.emit('player-changed', player)); player.connect('Seeked', this.emit.bind(this, 'player-seeked')); this._players.set(name, player); this.emit('player-added', player); } } catch (e) { debug(e, name); } } _removePlayer(name) { try { const player = this._players.get(name); if (player !== undefined) { this._paused.delete(name); this._players.delete(name); this.emit('player-removed', player); player.destroy(); } } catch (e) { debug(e, name); } } /** * Check for a player by its Identity. * * @param {string} identity - A player name * @return {boolean} %true if the player was found */ hasPlayer(identity) { for (const player of this._players.values()) { if (player.Identity === identity) return true; } return false; } /** * Get a player by its Identity. * * @param {string} identity - A player name * @return {GSConnectMPRISPlayer|null} A player or %null */ getPlayer(identity) { for (const player of this._players.values()) { if (player.Identity === identity) return player; } return null; } /** * Get a list of player identities. * * @return {string[]} A list of player identities */ getIdentities() { const identities = []; for (const player of this._players.values()) { const identity = player.Identity; if (identity) identities.push(identity); } return identities; } /** * A convenience function for pausing all players currently playing. */ pauseAll() { for (const [name, player] of this._players) { if (player.PlaybackStatus === 'Playing' && player.CanPause) { player.Pause(); this._paused.set(name, player); } } } /** * A convenience function for restarting all players paused with pauseAll(). */ unpauseAll() { for (const player of this._paused.values()) { if (player.PlaybackStatus === 'Paused' && player.CanPlay) player.Play(); } this._paused.clear(); } destroy() { if (this._cancellable.is_cancelled()) return; this._cancellable.cancel(); this._connection.signal_unsubscribe(this._nameOwnerChangedId); this._paused.clear(); this._players.forEach(player => player.destroy()); this._players.clear(); } }); /** * The service class for this component */ export default Manager; GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/notification.js000066400000000000000000000316141460766671100315110ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GjsPrivate from 'gi://GjsPrivate'; import GObject from 'gi://GObject'; import * as DBus from '../utils/dbus.js'; const _nodeInfo = Gio.DBusNodeInfo.new_for_xml(` `); const FDO_IFACE = _nodeInfo.lookup_interface('org.freedesktop.Notifications'); const FDO_MATCH = "interface='org.freedesktop.Notifications',member='Notify',type='method_call'"; const GTK_IFACE = _nodeInfo.lookup_interface('org.gtk.Notifications'); const GTK_MATCH = "interface='org.gtk.Notifications',member='AddNotification',type='method_call'"; /** * A class for snooping Freedesktop (libnotify) and Gtk (GNotification) * notifications and forwarding them to supporting devices. */ const Listener = GObject.registerClass({ GTypeName: 'GSConnectNotificationListener', Signals: { 'notification-added': { flags: GObject.SignalFlags.RUN_LAST, param_types: [GLib.Variant.$gtype], }, }, }, class Listener extends GObject.Object { _init() { super._init(); // Respect desktop notification settings this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications', }); // Watch for new application policies this._settingsId = this._settings.connect( 'changed::application-children', this._onSettingsChanged.bind(this) ); // Cache for appName->desktop-id lookups this._names = {}; // Asynchronous setup this._init_async(); } get applications() { if (this._applications === undefined) this._onSettingsChanged(); return this._applications; } /** * Update application notification settings */ _onSettingsChanged() { this._applications = {}; for (const app of this._settings.get_strv('application-children')) { const appSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications.application', path: `/org/gnome/desktop/notifications/application/${app}/`, }); const appInfo = Gio.DesktopAppInfo.new( appSettings.get_string('application-id') ); if (appInfo !== null) this._applications[appInfo.get_name()] = appSettings; } } async _listNames() { const reply = await this._session.call( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'ListNames', null, null, Gio.DBusCallFlags.NONE, -1, null); return reply.deepUnpack()[0]; } async _getNameOwner(name) { const reply = await this._session.call( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'GetNameOwner', new GLib.Variant('(s)', [name]), null, Gio.DBusCallFlags.NONE, -1, null); return reply.deepUnpack()[0]; } /** * Try and find a well-known name for @sender on the session bus * * @param {string} sender - A DBus unique name (eg. :1.2282) * @param {string} appName - @appName passed to Notify() (Optional) * @return {string} A well-known name or %null */ async _getAppId(sender, appName) { try { // Get a list of well-known names, ignoring @sender const names = await this._listNames(); names.splice(names.indexOf(sender), 1); // Make a short list for substring matches (fractal/org.gnome.Fractal) const appLower = appName.toLowerCase(); const shortList = names.filter(name => { return name.toLowerCase().includes(appLower); }); // Run the short list first for (const name of shortList) { const nameOwner = await this._getNameOwner(name); if (nameOwner === sender) return name; names.splice(names.indexOf(name), 1); } // Run the full list for (const name of names) { const nameOwner = await this._getNameOwner(name); if (nameOwner === sender) return name; } return null; } catch (e) { debug(e); return null; } } /** * Try and find the application name for @sender * * @param {string} sender - A DBus unique name * @param {string} [appName] - `appName` supplied by Notify() * @return {string} A well-known name or %null */ async _getAppName(sender, appName = null) { // Check the cache first if (appName && this._names.hasOwnProperty(appName)) return this._names[appName]; try { const appId = await this._getAppId(sender, appName); const appInfo = Gio.DesktopAppInfo.new(`${appId}.desktop`); this._names[appName] = appInfo.get_name(); appName = appInfo.get_name(); } catch (e) { // Silence errors } return appName; } /** * Callback for AddNotification()/Notify() * * @param {DBus.Interface} iface - The DBus interface * @param {string} name - The DBus method name * @param {GLib.Variant} parameters - The method parameters * @param {Gio.DBusMethodInvocation} invocation - The method invocation info */ async _onHandleMethodCall(iface, name, parameters, invocation) { try { // Check if notifications are disabled in desktop settings if (!this._settings.get_boolean('show-banners')) return; parameters = parameters.full_unpack(); // GNotification if (name === 'AddNotification') { this.AddNotification(...parameters); // libnotify } else if (name === 'Notify') { const message = invocation.get_message(); const destination = message.get_destination(); // Deduplicate notifications; only accept messages // directed to the notification bus, or its owner. if (destination !== 'org.freedesktop.Notifications') { if (this._fdoNameOwner === undefined) { this._fdoNameOwner = await this._getNameOwner( 'org.freedesktop.Notifications'); } if (this._fdoNameOwner !== destination) return; } // Try to brute-force an application name using DBus if (!this.applications.hasOwnProperty(parameters[0])) { const sender = message.get_sender(); parameters[0] = await this._getAppName(sender, parameters[0]); } this.Notify(...parameters); } } catch (e) { debug(e); } } /** * Export interfaces for proxying notifications and become a monitor * * @return {Promise} A promise for the operation */ _monitorConnection() { // libnotify Interface this._fdoNotifications = new GjsPrivate.DBusImplementation({ g_interface_info: FDO_IFACE, }); this._fdoMethodCallId = this._fdoNotifications.connect( 'handle-method-call', this._onHandleMethodCall.bind(this)); this._fdoNotifications.export(this._monitor, '/org/freedesktop/Notifications'); this._fdoNameOwnerChangedId = this._session.signal_subscribe( 'org.freedesktop.DBus', 'org.freedesktop.DBus', 'NameOwnerChanged', '/org/freedesktop/DBus', 'org.freedesktop.Notifications', Gio.DBusSignalFlags.MATCH_ARG0_NAMESPACE, this._onFdoNameOwnerChanged.bind(this) ); // GNotification Interface this._gtkNotifications = new GjsPrivate.DBusImplementation({ g_interface_info: GTK_IFACE, }); this._gtkMethodCallId = this._gtkNotifications.connect( 'handle-method-call', this._onHandleMethodCall.bind(this)); this._gtkNotifications.export(this._monitor, '/org/gtk/Notifications'); // Become a monitor for Fdo & Gtk notifications return this._monitor.call( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus.Monitoring', 'BecomeMonitor', new GLib.Variant('(asu)', [[FDO_MATCH, GTK_MATCH], 0]), null, Gio.DBusCallFlags.NONE, -1, null); } async _init_async() { try { this._session = Gio.DBus.session; this._monitor = await DBus.newConnection(); await this._monitorConnection(); } catch (e) { const service = Gio.Application.get_default(); if (service !== null) service.notify_error(e); else logError(e); } } _onFdoNameOwnerChanged(connection, sender, object, iface, signal, parameters) { this._fdoNameOwner = parameters.deepUnpack()[2]; } _sendNotification(notif) { // Check if this application is disabled in desktop settings const appSettings = this.applications[notif.appName]; if (appSettings && !appSettings.get_boolean('enable')) return; // Send the notification to each supporting device // TODO: avoid the overhead of the GAction framework with a signal? const variant = GLib.Variant.full_pack(notif); this.emit('notification-added', variant); } Notify(appName, replacesId, iconName, summary, body, actions, hints, timeout) { // Ignore notifications without an appName if (!appName) return; this._sendNotification({ appName: appName, id: `fdo|null|${replacesId}`, title: summary, text: body, ticker: `${summary}: ${body}`, isClearable: (replacesId !== 0), icon: iconName, }); } AddNotification(application, id, notification) { // Ignore our own notifications or we'll cause a notification loop if (application === 'org.gnome.Shell.Extensions.GSConnect') return; const appInfo = Gio.DesktopAppInfo.new(`${application}.desktop`); // Try to get an icon for the notification if (!notification.hasOwnProperty('icon')) notification.icon = appInfo.get_icon() || undefined; this._sendNotification({ appName: appInfo.get_name(), id: `gtk|${application}|${id}`, title: notification.title, text: notification.body, ticker: `${notification.title}: ${notification.body}`, isClearable: true, icon: notification.icon, }); } destroy() { try { if (this._fdoNotifications) { this._fdoNotifications.disconnect(this._fdoMethodCallId); this._fdoNotifications.unexport(); this._session.signal_unsubscribe(this._fdoNameOwnerChangedId); } if (this._gtkNotifications) { this._gtkNotifications.disconnect(this._gtkMethodCallId); this._gtkNotifications.unexport(); } if (this._settings) { this._settings.disconnect(this._settingsId); this._settings.run_dispose(); } // TODO: Gio.IOErrorEnum: The connection is closed // this._monitor.close_sync(null); GObject.signal_handlers_destroy(this); } catch (e) { debug(e); } } }); /** * The service class for this component */ export default Listener; GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/pulseaudio.js000066400000000000000000000152121460766671100311710ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GIRepository from 'gi://GIRepository'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Config from '../../config.js'; const Tweener = imports.tweener.tweener; let Gvc = null; try { // Add gnome-shell's typelib dir to the search path const typelibDir = GLib.build_filenamev([Config.GNOME_SHELL_LIBDIR, 'gnome-shell']); GIRepository.Repository.prepend_search_path(typelibDir); GIRepository.Repository.prepend_library_path(typelibDir); Gvc = (await import('gi://Gvc')).default; } catch (e) {} /** * Extend Gvc.MixerStream with a property for returning a user-visible name */ if (Gvc) { Object.defineProperty(Gvc.MixerStream.prototype, 'display_name', { get: function () { try { if (!this.get_ports().length) return this.description; return `${this.get_port().human_port} (${this.description})`; } catch (e) { return this.description; } }, }); } /** * A convenience wrapper for Gvc.MixerStream */ class Stream { constructor(mixer, stream) { this._mixer = mixer; this._stream = stream; this._max = mixer.get_vol_max_norm(); } get muted() { return this._stream.is_muted; } set muted(bool) { this._stream.change_is_muted(bool); } // Volume is a double in the range 0-1 get volume() { return Math.floor(100 * this._stream.volume / this._max) / 100; } set volume(num) { this._stream.volume = Math.floor(num * this._max); this._stream.push_volume(); } /** * Gradually raise or lower the stream volume to @value * * @param {number} value - A number in the range 0-1 * @param {number} [duration] - Duration to fade in seconds */ fade(value, duration = 1) { Tweener.removeTweens(this); if (this._stream.volume > value) { this._mixer.fading = true; Tweener.addTween(this, { volume: value, time: duration, transition: 'easeOutCubic', onComplete: () => { this._mixer.fading = false; }, }); } else if (this._stream.volume < value) { this._mixer.fading = true; Tweener.addTween(this, { volume: value, time: duration, transition: 'easeInCubic', onComplete: () => { this._mixer.fading = false; }, }); } } } /** * A subclass of Gvc.MixerControl with convenience functions for controlling the * default input/output volumes. * * The Mixer class uses GNOME Shell's Gvc library to control the system volume * and offers a few convenience functions. */ const Mixer = !Gvc ? null : GObject.registerClass({ GTypeName: 'GSConnectAudioMixer', }, class Mixer extends Gvc.MixerControl { _init(params) { super._init({name: 'GSConnect'}); this._previousVolume = undefined; this._volumeMuted = false; this._microphoneMuted = false; this.open(); } get fading() { if (this._fading === undefined) this._fading = false; return this._fading; } set fading(bool) { if (this.fading === bool) return; this._fading = bool; if (this.fading) this.emit('stream-changed', this._output._stream.id); } get input() { if (this._input === undefined) this.vfunc_default_source_changed(); return this._input; } get output() { if (this._output === undefined) this.vfunc_default_sink_changed(); return this._output; } vfunc_default_sink_changed(id) { try { const sink = this.get_default_sink(); this._output = (sink) ? new Stream(this, sink) : null; } catch (e) { logError(e); } } vfunc_default_source_changed(id) { try { const source = this.get_default_source(); this._input = (source) ? new Stream(this, source) : null; } catch (e) { logError(e); } } vfunc_state_changed(new_state) { try { if (new_state === Gvc.MixerControlState.READY) { this.vfunc_default_sink_changed(null); this.vfunc_default_source_changed(null); } } catch (e) { logError(e); } } /** * Store the current output volume then lower it to %15 * * @param {number} duration - Duration in seconds to fade */ lowerVolume(duration = 1) { try { if (this.output && this.output.volume > 0.15) { this._previousVolume = Number(this.output.volume); this.output.fade(0.15, duration); } } catch (e) { logError(e); } } /** * Mute the output volume (speakers) */ muteVolume() { try { if (!this.output || this.output.muted) return; this.output.muted = true; this._volumeMuted = true; } catch (e) { logError(e); } } /** * Mute the input volume (microphone) */ muteMicrophone() { try { if (!this.input || this.input.muted) return; this.input.muted = true; this._microphoneMuted = true; } catch (e) { logError(e); } } /** * Restore all mixer levels to their previous state */ restore() { try { // If we muted the microphone, unmute it before restoring the volume if (this._microphoneMuted) { this.input.muted = false; this._microphoneMuted = false; } // If we muted the volume, unmute it before restoring the volume if (this._volumeMuted) { this.output.muted = false; this._volumeMuted = false; } // If a previous volume is defined, raise it back up to that level if (this._previousVolume !== undefined) { this.output.fade(this._previousVolume); this._previousVolume = undefined; } } catch (e) { logError(e); } } destroy() { this.close(); } }); /** * The service class for this component */ export default Mixer; GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/session.js000066400000000000000000000042131460766671100305010ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; const Session = class { constructor() { this._connection = Gio.DBus.system; this._session = null; this._initAsync(); } async _initAsync() { try { const reply = await this._connection.call( 'org.freedesktop.login1', '/org/freedesktop/login1', 'org.freedesktop.login1.Manager', 'ListSessions', null, null, Gio.DBusCallFlags.NONE, -1, null); const sessions = reply.deepUnpack()[0]; const userName = GLib.get_user_name(); let sessionPath = '/org/freedesktop/login1/session/auto'; // eslint-disable-next-line no-unused-vars for (const [num, uid, name, seat, objectPath] of sessions) { if (name === userName) { sessionPath = objectPath; break; } } this._session = new Gio.DBusProxy({ g_connection: this._connection, g_name: 'org.freedesktop.login1', g_object_path: sessionPath, g_interface_name: 'org.freedesktop.login1.Session', }); await this._session.init_async(GLib.PRIORITY_DEFAULT, null); } catch (e) { this._session = null; logError(e); } } get idle() { if (this._session === null) return false; return this._session.get_cached_property('IdleHint').unpack(); } get locked() { if (this._session === null) return false; return this._session.get_cached_property('LockedHint').unpack(); } get active() { // Active if not idle and not locked return !(this.idle || this.locked); } destroy() { this._session = null; } }; /** * The service class for this component */ export default Session; GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/sound.js000066400000000000000000000113451460766671100301520ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; let GSound = null; try { GSound = (await import('gi://GSound')).default; } catch (e) {} const Player = class Player { constructor() { this._playing = new Set(); } get backend() { if (this._backend === undefined) { // Prefer GSound if (GSound !== null) { this._gsound = new GSound.Context(); this._gsound.init(null); this._backend = 'gsound'; // Try falling back to libcanberra, otherwise just re-run the test // in case one or the other is installed later } else if (GLib.find_program_in_path('canberra-gtk-play') !== null) { this._canberra = new Gio.SubprocessLauncher({ flags: Gio.SubprocessFlags.NONE, }); this._backend = 'libcanberra'; } else { return null; } } return this._backend; } _canberraPlaySound(name, cancellable) { const proc = this._canberra.spawnv(['canberra-gtk-play', '-i', name]); return proc.wait_check_async(cancellable); } async _canberraLoopSound(name, cancellable) { while (!cancellable.is_cancelled()) await this._canberraPlaySound(name, cancellable); } _gsoundPlaySound(name, cancellable) { return new Promise((resolve, reject) => { this._gsound.play_full( {'event.id': name}, cancellable, (source, res) => { try { resolve(source.play_full_finish(res)); } catch (e) { reject(e); } } ); }); } async _gsoundLoopSound(name, cancellable) { while (!cancellable.is_cancelled()) await this._gsoundPlaySound(name, cancellable); } _gdkPlaySound(name, cancellable) { if (this._display === undefined) this._display = Gdk.Display.get_default(); let count = 0; GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => { try { if (count++ < 4 && !cancellable.is_cancelled()) { this._display.beep(); return GLib.SOURCE_CONTINUE; } return GLib.SOURCE_REMOVE; } catch (e) { logError(e); return GLib.SOURCE_REMOVE; } }); return !cancellable.is_cancelled(); } _gdkLoopSound(name, cancellable) { this._gdkPlaySound(name, cancellable); GLib.timeout_add( GLib.PRIORITY_DEFAULT, 1500, this._gdkPlaySound.bind(this, name, cancellable) ); } async playSound(name, cancellable) { try { if (!(cancellable instanceof Gio.Cancellable)) cancellable = new Gio.Cancellable(); this._playing.add(cancellable); switch (this.backend) { case 'gsound': await this._gsoundPlaySound(name, cancellable); break; case 'canberra': await this._canberraPlaySound(name, cancellable); break; default: await this._gdkPlaySound(name, cancellable); } } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) logError(e); } finally { this._playing.delete(cancellable); } } async loopSound(name, cancellable) { try { if (!(cancellable instanceof Gio.Cancellable)) cancellable = new Gio.Cancellable(); this._playing.add(cancellable); switch (this.backend) { case 'gsound': await this._gsoundLoopSound(name, cancellable); break; case 'canberra': await this._canberraLoopSound(name, cancellable); break; default: await this._gdkLoopSound(name, cancellable); } } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) logError(e); } finally { this._playing.delete(cancellable); } } destroy() { for (const cancellable of this._playing) cancellable.cancel(); } }; /** * The service class for this component */ export default Player; GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/upower.js000066400000000000000000000123201460766671100303350ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; /** * The warning level of a battery. * * @readonly * @enum {number} */ const DeviceLevel = { UNKNOWN: 0, NONE: 1, DISCHARGING: 2, LOW: 3, CRITICAL: 4, ACTION: 5, NORMAL: 6, HIGH: 7, FULL: 8, LAST: 9, }; /** * The device state. * * @readonly * @enum {number} */ const DeviceState = { UNKNOWN: 0, CHARGING: 1, DISCHARGING: 2, EMPTY: 3, FULLY_CHARGED: 4, PENDING_CHARGE: 5, PENDING_DISCHARGE: 6, LAST: 7, }; /** * A class representing the system battery. */ const Battery = GObject.registerClass({ GTypeName: 'GSConnectSystemBattery', Signals: { 'changed': { flags: GObject.SignalFlags.RUN_FIRST, }, }, Properties: { 'charging': GObject.ParamSpec.boolean( 'charging', 'Charging', 'The current charging state.', GObject.ParamFlags.READABLE, false ), 'level': GObject.ParamSpec.int( 'level', 'Level', 'The current power level.', GObject.ParamFlags.READABLE, -1, 100, -1 ), 'threshold': GObject.ParamSpec.uint( 'threshold', 'Threshold', 'The current threshold state.', GObject.ParamFlags.READABLE, 0, 1, 0 ), }, }, class Battery extends GObject.Object { _init() { super._init(); this._cancellable = new Gio.Cancellable(); this._proxy = null; this._propertiesChangedId = 0; this._loadUPower(); } async _loadUPower() { try { this._proxy = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SYSTEM, g_name: 'org.freedesktop.UPower', g_object_path: '/org/freedesktop/UPower/devices/DisplayDevice', g_interface_name: 'org.freedesktop.UPower.Device', g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START, }); await this._proxy.init_async(GLib.PRIORITY_DEFAULT, this._cancellable); this._propertiesChangedId = this._proxy.connect( 'g-properties-changed', this._onPropertiesChanged.bind(this)); this._initProperties(this._proxy); } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { const service = Gio.Application.get_default(); if (service !== null) service.notify_error(e); else logError(e); } this._proxy = null; } } _initProperties(proxy) { if (proxy.g_name_owner === null) return; const percentage = proxy.get_cached_property('Percentage').unpack(); const state = proxy.get_cached_property('State').unpack(); const level = proxy.get_cached_property('WarningLevel').unpack(); this._level = Math.floor(percentage); this._charging = (state !== DeviceState.DISCHARGING); this._threshold = (!this.charging && level >= DeviceLevel.LOW); this.emit('changed'); } _onPropertiesChanged(proxy, changed, invalidated) { let emitChanged = false; const properties = changed.deepUnpack(); if (properties.hasOwnProperty('Percentage')) { emitChanged = true; const value = proxy.get_cached_property('Percentage').unpack(); this._level = Math.floor(value); this.notify('level'); } if (properties.hasOwnProperty('State')) { emitChanged = true; const value = proxy.get_cached_property('State').unpack(); this._charging = (value !== DeviceState.DISCHARGING); this.notify('charging'); } if (properties.hasOwnProperty('WarningLevel')) { emitChanged = true; const value = proxy.get_cached_property('WarningLevel').unpack(); this._threshold = (!this.charging && value >= DeviceLevel.LOW); this.notify('threshold'); } if (emitChanged) this.emit('changed'); } get charging() { if (this._charging === undefined) this._charging = false; return this._charging; } get is_present() { return (this._proxy && this._proxy.g_name_owner); } get level() { if (this._level === undefined) this._level = -1; return this._level; } get threshold() { if (this._threshold === undefined) this._threshold = 0; return this._threshold; } destroy() { if (this._cancellable.is_cancelled()) return; this._cancellable.cancel(); if (this._proxy && this._propertiesChangedId > 0) { this._proxy.disconnect(this._propertiesChangedId); this._propertiesChangedId = 0; } } }); /** * The service class for this component */ export default Battery; GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/components/ydotool.js000066400000000000000000000072311460766671100305120ustar00rootroot00000000000000// SPDX-FileCopyrightText: JingMatrix https://github.com/JingMatrix // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import Gdk from 'gi://Gdk'; const keyCodes = new Map([ ['1', 2], ['2', 3], ['3', 4], ['4', 5], ['5', 6], ['6', 7], ['7', 8], ['8', 9], ['9', 10], ['0', 11], ['-', 12], ['=', 13], ['Q', 16], ['W', 17], ['E', 18], ['R', 19], ['T', 20], ['Y', 21], ['U', 22], ['I', 23], ['O', 24], ['P', 25], ['[', 26], [']', 27], ['A', 30], ['S', 31], ['D', 32], ['F', 33], ['G', 34], ['H', 35], ['J', 36], ['K', 37], ['L', 38], [';', 39], ["'", 40], ['Z', 44], ['X', 45], ['C', 46], ['V', 47], ['B', 48], ['N', 49], ['M', 50], [',', 51], ['.', 52], ['/', 53], ['\\', 43], ]); export default class Controller { constructor() { // laucher for wl-clipboard this._launcher = new Gio.SubprocessLauncher({ flags: Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_MERGE, }); this._args = []; this.buttonMap = new Map([ [Gdk.BUTTON_PRIMARY, '0'], [Gdk.BUTTON_MIDDLE, '2'], [Gdk.BUTTON_SECONDARY, '1'], ]); } get args() { return this._args; } set args(opts) { this._args = ['ydotool'].concat(opts); try { this._launcher.spawnv(this._args); } catch (e) { debug(e, this._args); } } /* * Pointer Events */ movePointer(dx, dy) { if (dx === 0 && dy === 0) return; this.args = ['mousemove', '--', dx.toString(), dy.toString()]; } pressPointer(button) { this.args = ['click', '0x4' + this.buttonMap.get(button)]; } releasePointer(button) { this.args = ['click', '0x8' + this.buttonMap.get(button)]; } clickPointer(button) { this.args = ['click', '0xC' + this.buttonMap.get(button)]; } doubleclickPointer(button) { this.args = [ 'click', '0xC' + this.buttonMap.get(button), 'click', '0xC' + this.buttonMap.get(button), ]; } scrollPointer(dx, dy) { if (dx === 0 && dy === 0) return; this.args = ['mousemove', '-w', '--', dx.toString(), dy.toString()]; } /* * Keyboard Events */ pressKeys(input, modifiers_codes) { if (typeof input === 'string' && modifiers_codes.length === 0) { try { this._launcher.spawnv(['wtype', input]); } catch (e) { debug(e); this.arg = ['type', '--', input]; } } else { if (typeof input === 'number') { modifiers_codes.push(input); } else if (typeof input === 'string') { input = input.toUpperCase(); for (let i = 0; i < input.length; i++) { if (keyCodes.get(input[i])) { modifiers_codes.push(keyCodes.get(input[i])); } else { debug('Keycode for ' + input[i] + ' not found'); return; } } } this._args = ['key']; modifiers_codes.forEach((code) => this._args.push(code + ':1')); modifiers_codes .reverse() .forEach((code) => this._args.push(code + ':0')); this.args = this._args; } } destroy() { this._args = []; } } GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/core.js000066400000000000000000000427721460766671100255750ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import plugins from './plugins/index.js'; /** * Get the local device type. * * @return {string} A device type string */ export function _getDeviceType() { try { let type = GLib.file_get_contents('/sys/class/dmi/id/chassis_type')[1]; type = Number(new TextDecoder().decode(type)); if ([8, 9, 10, 14].includes(type)) return 'laptop'; return 'desktop'; } catch (e) { return 'desktop'; } } /** * The packet class is a simple Object-derived class, offering some conveniences * for working with KDE Connect packets. */ export class Packet { constructor(data = null) { this.id = 0; this.type = undefined; this.body = {}; if (typeof data === 'string') Object.assign(this, JSON.parse(data)); else if (data !== null) Object.assign(this, data); } [Symbol.toPrimitive](hint) { this.id = Date.now(); if (hint === 'string') return `${JSON.stringify(this)}\n`; if (hint === 'number') return `${JSON.stringify(this)}\n`.length; return true; } get [Symbol.toStringTag]() { return `Packet:${this.type}`; } /** * Deserialize and return a new Packet from an Object or string. * * @param {Object|string} data - A string or dictionary to deserialize * @return {Core.Packet} A new packet object */ static deserialize(data) { return new Packet(data); } /** * Serialize the packet as a single line with a terminating new-line (`\n`) * character, ready to be written to a channel. * * @return {string} A serialized packet */ serialize() { this.id = Date.now(); return `${JSON.stringify(this)}\n`; } /** * Update the packet from a dictionary or string of JSON * * @param {Object|string} data - Source data */ update(data) { try { if (typeof data === 'string') Object.assign(this, JSON.parse(data)); else Object.assign(this, data); } catch (e) { throw Error(`Malformed data: ${e.message}`); } } /** * Check if the packet has a payload. * * @return {boolean} %true if @packet has a payload */ hasPayload() { if (!this.hasOwnProperty('payloadSize')) return false; if (!this.hasOwnProperty('payloadTransferInfo')) return false; return (Object.keys(this.payloadTransferInfo).length > 0); } } /** * Channel objects handle KDE Connect packet exchange and data transfers for * devices. The implementation is responsible for all negotiation of the * underlying protocol. */ export const Channel = GObject.registerClass({ GTypeName: 'GSConnectChannel', Properties: { 'closed': GObject.ParamSpec.boolean( 'closed', 'Closed', 'Whether the channel has been closed', GObject.ParamFlags.READABLE, false ), }, }, class Channel extends GObject.Object { get address() { throw new GObject.NotImplementedError(); } get backend() { if (this._backend === undefined) this._backend = null; return this._backend; } set backend(backend) { this._backend = backend; } get cancellable() { if (this._cancellable === undefined) this._cancellable = new Gio.Cancellable(); return this._cancellable; } get closed() { if (this._closed === undefined) this._closed = false; return this._closed; } get input_stream() { if (this._input_stream === undefined) { if (this._connection instanceof Gio.IOStream) return this._connection.get_input_stream(); return null; } return this._input_stream; } set input_stream(stream) { this._input_stream = stream; } get output_stream() { if (this._output_stream === undefined) { if (this._connection instanceof Gio.IOStream) return this._connection.get_output_stream(); return null; } return this._output_stream; } set output_stream(stream) { this._output_stream = stream; } get uuid() { if (this._uuid === undefined) this._uuid = GLib.uuid_string_random(); return this._uuid; } set uuid(uuid) { this._uuid = uuid; } /** * Close the channel. */ close() { throw new GObject.NotImplementedError(); } /** * Read a packet. * * @param {Gio.Cancellable} [cancellable] - A cancellable * @return {Promise} The packet */ async readPacket(cancellable = null) { if (cancellable === null) cancellable = this.cancellable; if (!(this.input_stream instanceof Gio.DataInputStream)) { this.input_stream = new Gio.DataInputStream({ base_stream: this.input_stream, }); } const [data] = await this.input_stream.read_line_async( GLib.PRIORITY_DEFAULT, cancellable); if (data === null) { throw new Gio.IOErrorEnum({ message: 'End of stream', code: Gio.IOErrorEnum.CONNECTION_CLOSED, }); } return new Packet(data); } /** * Send a packet. * * @param {Core.Packet} packet - The packet to send * @param {Gio.Cancellable} [cancellable] - A cancellable * @return {Promise} %true if successful */ sendPacket(packet, cancellable = null) { if (cancellable === null) cancellable = this.cancellable; return this.output_stream.write_all_async(packet.serialize(), GLib.PRIORITY_DEFAULT, cancellable); } /** * Reject a transfer. * * @param {Core.Packet} packet - A packet with payload info */ rejectTransfer(packet) { throw new GObject.NotImplementedError(); } /** * Download a payload from a device. Typically implementations will override * this with an async function. * * @param {Core.Packet} packet - A packet * @param {Gio.OutputStream} target - The target stream * @param {Gio.Cancellable} [cancellable] - A cancellable for the upload */ download(packet, target, cancellable = null) { throw new GObject.NotImplementedError(); } /** * Upload a payload to a device. Typically implementations will override * this with an async function. * * @param {Core.Packet} packet - The packet describing the transfer * @param {Gio.InputStream} source - The source stream * @param {number} size - The payload size * @param {Gio.Cancellable} [cancellable] - A cancellable for the upload */ upload(packet, source, size, cancellable = null) { throw new GObject.NotImplementedError(); } }); /** * ChannelService implementations provide Channel objects, emitting the * ChannelService::channel signal when a new connection has been accepted. */ export const ChannelService = GObject.registerClass({ GTypeName: 'GSConnectChannelService', Properties: { 'active': GObject.ParamSpec.boolean( 'active', 'Active', 'Whether the service is active', GObject.ParamFlags.READABLE, false ), 'id': GObject.ParamSpec.string( 'id', 'ID', 'The hostname or other network unique id', GObject.ParamFlags.READWRITE, null ), 'name': GObject.ParamSpec.string( 'name', 'Name', 'The name of the backend', GObject.ParamFlags.READWRITE, null ), }, Signals: { 'channel': { flags: GObject.SignalFlags.RUN_LAST, param_types: [Channel.$gtype], return_type: GObject.TYPE_BOOLEAN, }, }, }, class ChannelService extends GObject.Object { get active() { if (this._active === undefined) this._active = false; return this._active; } get cancellable() { if (this._cancellable === undefined) this._cancellable = new Gio.Cancellable(); return this._cancellable; } get name() { if (this._name === undefined) this._name = GLib.get_host_name(); return this._name; } set name(name) { if (this.name === name) return; this._name = name; this.notify('name'); } get id() { if (this._id === undefined) this._id = GLib.uuid_string_random(); return this._id; } set id(id) { if (this.id === id) return; this._id = id; } get identity() { if (this._identity === undefined) this.buildIdentity(); return this._identity; } /** * Broadcast directly to @address or the whole network if %null * * @param {string} [address] - A string address */ broadcast(address = null) { throw new GObject.NotImplementedError(); } /** * Rebuild the identity packet used to identify the local device. An * implementation may override this to make modifications to the default * capabilities if necessary (eg. bluez without SFTP support). */ buildIdentity() { this._identity = new Packet({ id: 0, type: 'kdeconnect.identity', body: { deviceId: this.id, deviceName: this.name, deviceType: _getDeviceType(), protocolVersion: 7, incomingCapabilities: [], outgoingCapabilities: [], }, }); for (const name in plugins) { const meta = plugins[name].Metadata; if (meta === undefined) continue; for (const type of meta.incomingCapabilities) this._identity.body.incomingCapabilities.push(type); for (const type of meta.outgoingCapabilities) this._identity.body.outgoingCapabilities.push(type); } } /** * Emit Core.ChannelService::channel * * @param {Core.Channel} channel - The new channel */ channel(channel) { if (!this.emit('channel', channel)) channel.close(); } /** * Start the channel service. Implementations should throw an error if the * service fails to meet any of its requirements for opening or accepting * connections. */ start() { throw new GObject.NotImplementedError(); } /** * Stop the channel service. */ stop() { throw new GObject.NotImplementedError(); } /** * Destroy the channel service. */ destroy() { } }); /** * A class representing a file transfer. */ export const Transfer = GObject.registerClass({ GTypeName: 'GSConnectTransfer', Properties: { 'channel': GObject.ParamSpec.object( 'channel', 'Channel', 'The channel that owns this transfer', GObject.ParamFlags.READWRITE, Channel.$gtype ), 'completed': GObject.ParamSpec.boolean( 'completed', 'Completed', 'Whether the transfer has completed', GObject.ParamFlags.READABLE, false ), 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device that created this transfer', GObject.ParamFlags.READWRITE, GObject.Object.$gtype ), }, }, class Transfer extends GObject.Object { _init(params = {}) { super._init(params); this._cancellable = new Gio.Cancellable(); this._items = []; } get channel() { if (this._channel === undefined) this._channel = null; return this._channel; } set channel(channel) { if (this.channel === channel) return; this._channel = channel; } get completed() { if (this._completed === undefined) this._completed = false; return this._completed; } get device() { if (this._device === undefined) this._device = null; return this._device; } set device(device) { if (this.device === device) return; this._device = device; } get uuid() { if (this._uuid === undefined) this._uuid = GLib.uuid_string_random(); return this._uuid; } /** * Ensure there is a stream for the transfer item. * * @param {Object} item - A transfer item * @param {Gio.Cancellable} [cancellable] - A cancellable */ async _ensureStream(item, cancellable = null) { // This is an upload from a remote device if (item.packet.hasPayload()) { if (item.target instanceof Gio.OutputStream) return; if (item.file instanceof Gio.File) { item.target = await item.file.replace_async( null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, GLib.PRIORITY_DEFAULT, this._cancellable); } } else { if (item.source instanceof Gio.InputStream) return; if (item.file instanceof Gio.File) { const read = item.file.read_async(GLib.PRIORITY_DEFAULT, cancellable); const query = item.file.query_info_async( Gio.FILE_ATTRIBUTE_STANDARD_SIZE, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, cancellable); const [stream, info] = await Promise.all([read, query]); item.source = stream; item.size = info.get_size(); } } } /** * Add a file to the transfer. * * @param {Core.Packet} packet - A packet * @param {Gio.File} file - A file to transfer */ addFile(packet, file) { const item = { packet: new Packet(packet), file: file, source: null, target: null, }; this._items.push(item); } /** * Add a filepath to the transfer. * * @param {Core.Packet} packet - A packet * @param {string} path - A filepath to transfer */ addPath(packet, path) { const item = { packet: new Packet(packet), file: Gio.File.new_for_path(path), source: null, target: null, }; this._items.push(item); } /** * Add a stream to the transfer. * * @param {Core.Packet} packet - A packet * @param {Gio.InputStream|Gio.OutputStream} stream - A stream to transfer * @param {number} [size] - Payload size */ addStream(packet, stream, size = 0) { const item = { packet: new Packet(packet), file: null, source: null, target: null, size: size, }; if (stream instanceof Gio.InputStream) item.source = stream; else if (stream instanceof Gio.OutputStream) item.target = stream; this._items.push(item); } /** * Execute a transfer operation. Implementations may override this, while * the default uses g_output_stream_splice(). * * @param {Gio.Cancellable} [cancellable] - A cancellable */ async start(cancellable = null) { let error = null; try { let item; // If a cancellable is passed in, chain to its signal if (cancellable instanceof Gio.Cancellable) cancellable.connect(() => this._cancellable.cancel()); while ((item = this._items.shift())) { // If created for a device, ignore connection changes by // ensuring we have the most recent channel if (this.device !== null) this._channel = this.device.channel; // TODO: transfer queueing? if (this.channel === null || this.channel.closed) { throw new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.CONNECTION_CLOSED, message: 'Channel is closed', }); } await this._ensureStream(item, this._cancellable); if (item.packet.hasPayload()) { await this.channel.download(item.packet, item.target, this._cancellable); } else { await this.channel.upload(item.packet, item.source, item.size, this._cancellable); } } } catch (e) { error = e; } finally { this._completed = true; this.notify('completed'); } if (error !== null) throw error; } cancel() { if (this._cancellable.is_cancelled() === false) this._cancellable.cancel(); } }); GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/daemon.js000077500000000000000000000467501460766671100261130ustar00rootroot00000000000000#!/usr/bin/env -S gjs -m // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk?version=3.0'; import 'gi://GdkPixbuf?version=2.0'; import Gio from 'gi://Gio?version=2.0'; import 'gi://GIRepository?version=2.0'; import GLib from 'gi://GLib?version=2.0'; import GObject from 'gi://GObject?version=2.0'; import Gtk from 'gi://Gtk?version=3.0'; import 'gi://Pango?version=1.0'; import system from 'system'; import './init.js'; import Config from '../config.js'; import Manager from './manager.js'; import * as ServiceUI from './ui/service.js'; import('gi://GioUnix?version=2.0').catch(() => {}); // Set version for optional dependency /** * Class representing the GSConnect service daemon. */ const Service = GObject.registerClass({ GTypeName: 'GSConnectService', }, class Service extends Gtk.Application { _init() { super._init({ application_id: 'org.gnome.Shell.Extensions.GSConnect', flags: Gio.ApplicationFlags.HANDLES_OPEN, resource_base_path: '/org/gnome/Shell/Extensions/GSConnect', }); GLib.set_prgname('gsconnect'); GLib.set_application_name('GSConnect'); // Command-line this._initOptions(); } get settings() { if (this._settings === undefined) { this._settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup(Config.APP_ID, true), }); } return this._settings; } /* * GActions */ _initActions() { const actions = [ ['connect', this._identify.bind(this), new GLib.VariantType('s')], ['device', this._device.bind(this), new GLib.VariantType('(ssbv)')], ['error', this._error.bind(this), new GLib.VariantType('a{ss}')], ['preferences', this._preferences, null], ['quit', () => this.quit(), null], ['refresh', this._identify.bind(this), null], ]; for (const [name, callback, type] of actions) { const action = new Gio.SimpleAction({ name: name, parameter_type: type, }); action.connect('activate', callback); this.add_action(action); } } /** * A wrapper for Device GActions. This is used to route device notification * actions to their device, since GNotifications need an 'app' level action. * * @param {Gio.Action} action - The GAction * @param {GLib.Variant} parameter - The activation parameter */ _device(action, parameter) { try { parameter = parameter.unpack(); // Select the appropriate device(s) let devices; const id = parameter[0].unpack(); if (id === '*') devices = this.manager.devices.values(); else devices = [this.manager.devices.get(id)]; // Unpack the action data and activate the action const name = parameter[1].unpack(); const target = parameter[2].unpack() ? parameter[3].unpack() : null; for (const device of devices) device.activate_action(name, target); } catch (e) { logError(e); } } _error(action, parameter) { try { const error = parameter.deepUnpack(); // If there's a URL, we have better information in the Wiki if (error.url !== undefined) { Gio.AppInfo.launch_default_for_uri_async( error.url, null, null, null ); return; } const dialog = new ServiceUI.ErrorDialog(error); dialog.present(); } catch (e) { logError(e); } } _identify(action, parameter) { try { let uri = null; if (parameter instanceof GLib.Variant) uri = parameter.unpack(); this.manager.identify(uri); } catch (e) { logError(e); } } _preferences() { Gio.Subprocess.new( [`${Config.PACKAGE_DATADIR}/gsconnect-preferences`], Gio.SubprocessFlags.NONE ); } /** * Report a service-level error * * @param {Object} error - An Error or object with name, message and stack */ notify_error(error) { try { // Always log the error logError(error); // Create an new notification let id, body, priority; const notif = new Gio.Notification(); const icon = new Gio.ThemedIcon({name: 'dialog-error'}); let target = null; if (error.name === undefined) error.name = 'Error'; if (error.url !== undefined) { id = error.url; body = _('Click for help troubleshooting'); priority = Gio.NotificationPriority.URGENT; target = new GLib.Variant('a{ss}', { name: error.name.trim(), message: error.message.trim(), stack: error.stack.trim(), url: error.url, }); } else { id = error.message.trim(); body = _('Click for more information'); priority = Gio.NotificationPriority.HIGH; target = new GLib.Variant('a{ss}', { name: error.name.trim(), message: error.message.trim(), stack: error.stack.trim(), }); } notif.set_title(`GSConnect: ${error.name.trim()}`); notif.set_body(body); notif.set_icon(icon); notif.set_priority(priority); notif.set_default_action_and_target('app.error', target); this.send_notification(id, notif); } catch (e) { logError(e); } } vfunc_activate() { super.vfunc_activate(); } vfunc_startup() { super.vfunc_startup(); this.hold(); // Watch *this* file and stop the service if it's updated/uninstalled this._serviceMonitor = Gio.File.new_for_path( `${Config.PACKAGE_DATADIR}/service/daemon.js` ).monitor(Gio.FileMonitorFlags.WATCH_MOVES, null); this._serviceMonitor.connect('changed', () => this.quit()); // Init some resources const provider = new Gtk.CssProvider(); provider.load_from_resource(`${Config.APP_PATH}/application.css`); Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ); // Ensure our handlers are registered try { const appInfo = Gio.DesktopAppInfo.new(`${Config.APP_ID}.desktop`); appInfo.add_supports_type('x-scheme-handler/sms'); appInfo.add_supports_type('x-scheme-handler/tel'); } catch (e) { debug(e); } // GActions & GSettings this._initActions(); this.manager.start(); } vfunc_dbus_register(connection, object_path) { if (!super.vfunc_dbus_register(connection, object_path)) return false; this.manager = new Manager({ connection: connection, object_path: object_path, }); return true; } vfunc_dbus_unregister(connection, object_path) { this.manager.destroy(); super.vfunc_dbus_unregister(connection, object_path); } vfunc_open(files, hint) { super.vfunc_open(files, hint); for (const file of files) { let action, parameter, title; try { switch (file.get_uri_scheme()) { case 'sms': title = _('Send SMS'); action = 'uriSms'; parameter = new GLib.Variant('s', file.get_uri()); break; case 'tel': title = _('Dial Number'); action = 'shareUri'; parameter = new GLib.Variant('s', file.get_uri()); break; case 'file': title = _('Share File'); action = 'shareFile'; parameter = new GLib.Variant('(sb)', [file.get_uri(), false]); break; default: throw new Error(`Unsupported URI: ${file.get_uri()}`); } // Show chooser dialog new ServiceUI.DeviceChooser({ title: title, action_name: action, action_target: parameter, }); } catch (e) { logError(e, `GSConnect: Opening ${file.get_uri()}`); } } } vfunc_shutdown() { // Dispose GSettings if (this._settings !== undefined) this.settings.run_dispose(); this.manager.stop(); // Exhaust the event loop to ensure any pending operations complete const context = GLib.MainContext.default(); while (context.iteration(false)) continue; // Force a GC to prevent any more calls back into JS, then chain-up system.gc(); super.vfunc_shutdown(); } /* * CLI */ _initOptions() { /* * Device Listings */ this.add_main_option( 'list-devices', 'l'.charCodeAt(0), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('List available devices'), null ); this.add_main_option( 'list-all', 'a'.charCodeAt(0), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('List all devices'), null ); this.add_main_option( 'device', 'd'.charCodeAt(0), GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Target Device'), '' ); /** * Pairing */ this.add_main_option( 'pair', 0, GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('Pair'), null ); this.add_main_option( 'unpair', 0, GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('Unpair'), null ); /* * Messaging */ this.add_main_option( 'message', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING_ARRAY, _('Send SMS'), '' ); this.add_main_option( 'message-body', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Message Body'), '' ); /* * Notifications */ this.add_main_option( 'notification', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Send Notification'), '' ); this.add_main_option( 'notification-appname', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Notification App Name'), '<name>' ); this.add_main_option( 'notification-body', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Notification Body'), '<text>' ); this.add_main_option( 'notification-icon', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Notification Icon'), '<icon-name>' ); this.add_main_option( 'notification-id', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Notification ID'), '<id>' ); this.add_main_option( 'ping', 0, GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('Ping'), null ); this.add_main_option( 'ring', 0, GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('Ring'), null ); /* * Sharing */ this.add_main_option( 'share-file', 0, GLib.OptionFlags.NONE, GLib.OptionArg.FILENAME_ARRAY, _('Share File'), '<filepath|URI>' ); this.add_main_option( 'share-link', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING_ARRAY, _('Share Link'), '<URL>' ); this.add_main_option( 'share-text', 0, GLib.OptionFlags.NONE, GLib.OptionArg.STRING, _('Share Text'), '<text>' ); /* * Misc */ this.add_main_option( 'version', 'v'.charCodeAt(0), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('Show release version'), null ); } _cliAction(id, name, parameter = null) { const parameters = []; if (parameter instanceof GLib.Variant) parameters[0] = parameter; id = id.replace(/\W+/g, '_'); Gio.DBus.session.call_sync( 'org.gnome.Shell.Extensions.GSConnect', `/org/gnome/Shell/Extensions/GSConnect/Device/${id}`, 'org.gtk.Actions', 'Activate', GLib.Variant.new('(sava{sv})', [name, parameters, {}]), null, Gio.DBusCallFlags.NONE, -1, null ); } _cliListDevices(full = true) { const result = Gio.DBus.session.call_sync( 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', 'org.freedesktop.DBus.ObjectManager', 'GetManagedObjects', null, null, Gio.DBusCallFlags.NONE, -1, null ); const variant = result.unpack()[0].unpack(); let device; for (let object of Object.values(variant)) { object = object.recursiveUnpack(); device = object['org.gnome.Shell.Extensions.GSConnect.Device']; if (full) print(`${device.Id}\t${device.Name}\t${device.Connected}\t${device.Paired}`); else if (device.Connected && device.Paired) print(device.Id); } } _cliMessage(id, options) { if (!options.contains('message-body')) throw new TypeError('missing --message-body option'); // TODO: currently we only support single-recipient messaging const addresses = options.lookup_value('message', null).deepUnpack(); const body = options.lookup_value('message-body', null).deepUnpack(); this._cliAction( id, 'sendSms', GLib.Variant.new('(ss)', [addresses[0], body]) ); } _cliNotify(id, options) { const title = options.lookup_value('notification', null).unpack(); let body = ''; let icon = null; let nid = `${Date.now()}`; let appName = 'GSConnect CLI'; if (options.contains('notification-id')) nid = options.lookup_value('notification-id', null).unpack(); if (options.contains('notification-body')) body = options.lookup_value('notification-body', null).unpack(); if (options.contains('notification-appname')) appName = options.lookup_value('notification-appname', null).unpack(); if (options.contains('notification-icon')) { icon = options.lookup_value('notification-icon', null).unpack(); icon = Gio.Icon.new_for_string(icon); } else { icon = new Gio.ThemedIcon({ name: 'org.gnome.Shell.Extensions.GSConnect', }); } const notification = new GLib.Variant('a{sv}', { appName: GLib.Variant.new_string(appName), id: GLib.Variant.new_string(nid), title: GLib.Variant.new_string(title), text: GLib.Variant.new_string(body), ticker: GLib.Variant.new_string(`${title}: ${body}`), time: GLib.Variant.new_string(`${Date.now()}`), isClearable: GLib.Variant.new_boolean(true), icon: icon.serialize(), }); this._cliAction(id, 'sendNotification', notification); } _cliShareFile(device, options) { const files = options.lookup_value('share-file', null).deepUnpack(); for (let file of files) { file = new TextDecoder().decode(file); this._cliAction(device, 'shareFile', GLib.Variant.new('(sb)', [file, false])); } } _cliShareLink(device, options) { const uris = options.lookup_value('share-link', null).unpack(); for (const uri of uris) this._cliAction(device, 'shareUri', uri); } _cliShareText(device, options) { const text = options.lookup_value('share-text', null).unpack(); this._cliAction(device, 'shareText', GLib.Variant.new_string(text)); } vfunc_handle_local_options(options) { try { if (options.contains('version')) { print(`GSConnect ${Config.PACKAGE_VERSION}`); return 0; } this.register(null); if (options.contains('list-devices')) { this._cliListDevices(false); return 0; } if (options.contains('list-all')) { this._cliListDevices(true); return 0; } // We need a device for anything else; exit since this is probably // the daemon being started. if (!options.contains('device')) return -1; const id = options.lookup_value('device', null).unpack(); // Pairing if (options.contains('pair')) { this._cliAction(id, 'pair'); return 0; } if (options.contains('unpair')) { this._cliAction(id, 'unpair'); return 0; } // Plugins if (options.contains('message')) this._cliMessage(id, options); if (options.contains('notification')) this._cliNotify(id, options); if (options.contains('ping')) this._cliAction(id, 'ping', GLib.Variant.new_string('')); if (options.contains('ring')) this._cliAction(id, 'ring'); if (options.contains('share-file')) this._cliShareFile(id, options); if (options.contains('share-link')) this._cliShareLink(id, options); if (options.contains('share-text')) this._cliShareText(id, options); return 0; } catch (e) { logError(e); return 1; } } }); await (new Service()).runAsync([system.programInvocationName].concat(ARGV)); ������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/device.js�����������������������������0000664�0000000�0000000�00000101017�14607666711�0026070�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Config from '../config.js'; import * as Components from './components/index.js'; import * as Core from './core.js'; import plugins from './plugins/index.js'; /** * An object representing a remote device. * * Device class is subclassed from Gio.SimpleActionGroup so it implements the * GActionGroup and GActionMap interfaces, like Gio.Application. * */ const Device = GObject.registerClass({ GTypeName: 'GSConnectDevice', Properties: { 'connected': GObject.ParamSpec.boolean( 'connected', 'Connected', 'Whether the device is connected', GObject.ParamFlags.READABLE, false ), 'contacts': GObject.ParamSpec.object( 'contacts', 'Contacts', 'The contacts store for this device', GObject.ParamFlags.READABLE, GObject.Object ), 'encryption-info': GObject.ParamSpec.string( 'encryption-info', 'Encryption Info', 'A formatted string with the local and remote fingerprints', GObject.ParamFlags.READABLE, null ), 'icon-name': GObject.ParamSpec.string( 'icon-name', 'Icon Name', 'Icon name representing the device', GObject.ParamFlags.READABLE, null ), 'id': GObject.ParamSpec.string( 'id', 'Id', 'The device hostname or other network unique id', GObject.ParamFlags.READABLE, '' ), 'name': GObject.ParamSpec.string( 'name', 'Name', 'The device name', GObject.ParamFlags.READABLE, null ), 'paired': GObject.ParamSpec.boolean( 'paired', 'Paired', 'Whether the device is paired', GObject.ParamFlags.READABLE, false ), 'type': GObject.ParamSpec.string( 'type', 'Type', 'The device type', GObject.ParamFlags.READABLE, null ), }, }, class Device extends Gio.SimpleActionGroup { _init(identity) { super._init(); this._id = identity.body.deviceId; // GLib.Source timeout id's for pairing requests this._incomingPairRequest = 0; this._outgoingPairRequest = 0; // Maps of name->Plugin, packet->Plugin, uuid->Transfer this._plugins = new Map(); this._handlers = new Map(); this._procs = new Set(); this._transfers = new Map(); this._outputLock = false; this._outputQueue = []; // GSettings this.settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect.Device', true ), path: `/org/gnome/shell/extensions/gsconnect/device/${this.id}/`, }); this._migratePlugins(); // Watch for changes to supported and disabled plugins this._disabledPluginsChangedId = this.settings.connect( 'changed::disabled-plugins', this._onAllowedPluginsChanged.bind(this) ); this._supportedPluginsChangedId = this.settings.connect( 'changed::supported-plugins', this._onAllowedPluginsChanged.bind(this) ); this._registerActions(); this.menu = new Gio.Menu(); // Parse identity if initialized with a proper packet, otherwise load if (identity.id !== undefined) this._handleIdentity(identity); else this._loadPlugins(); } get channel() { if (this._channel === undefined) this._channel = null; return this._channel; } get connected() { if (this._connected === undefined) this._connected = false; return this._connected; } get connection_type() { const lastConnection = this.settings.get_string('last-connection'); return lastConnection.split('://')[0]; } get contacts() { const contacts = this._plugins.get('contacts'); if (contacts && contacts.settings.get_boolean('contacts-source')) return contacts._store; if (this._contacts === undefined) this._contacts = Components.acquire('contacts'); return this._contacts; } // FIXME: backend should do this stuff get encryption_info() { let localCert = null; let remoteCert = null; // Bluetooth connections have no certificate so we use the host address if (this.connection_type === 'bluetooth') { // TRANSLATORS: Bluetooth address for remote device return _('Bluetooth device at %s').format('???'); // If the device is connected use the certificate from the connection } else if (this.connected) { remoteCert = this.channel.peer_certificate; // Otherwise pull it out of the settings } else if (this.paired) { remoteCert = Gio.TlsCertificate.new_from_pem( this.settings.get_string('certificate-pem'), -1 ); } // FIXME: another ugly reach-around let lanBackend; if (this.service !== null) lanBackend = this.service.manager.backends.get('lan'); if (lanBackend && lanBackend.certificate) localCert = lanBackend.certificate; let verificationKey = ''; if (localCert && remoteCert) { let a = localCert.pubkey_der(); let b = remoteCert.pubkey_der(); if (a.compare(b) < 0) [a, b] = [b, a]; // swap const checksum = new GLib.Checksum(GLib.ChecksumType.SHA256); checksum.update(a.toArray()); checksum.update(b.toArray()); verificationKey = checksum.get_string(); } // TRANSLATORS: Label for TLS connection verification key // // Example: // // Verification key: 0123456789abcdef000000000000000000000000 return _('Verification key: %s').format(verificationKey); } get id() { return this._id; } get name() { return this.settings.get_string('name'); } get paired() { return this.settings.get_boolean('paired'); } get icon_name() { switch (this.type) { case 'laptop': return 'laptop-symbolic'; case 'phone': return 'smartphone-symbolic'; case 'tablet': return 'tablet-symbolic'; case 'tv': return 'tv-symbolic'; case 'desktop': default: return 'computer-symbolic'; } } get service() { if (this._service === undefined) this._service = Gio.Application.get_default(); return this._service; } get type() { return this.settings.get_string('type'); } _migratePlugins() { const deprecated = ['photo']; const supported = this.settings .get_strv('supported-plugins') .filter(name => !deprecated.includes(name)); this.settings.set_strv('supported-plugins', supported); } _handleIdentity(packet) { this.freeze_notify(); // If we're connected, record the reconnect URI if (this.channel !== null) this.settings.set_string('last-connection', this.channel.address); // The type won't change, but it might not be properly set yet if (this.type !== packet.body.deviceType) { this.settings.set_string('type', packet.body.deviceType); this.notify('type'); this.notify('icon-name'); } // The name may change so we check and notify if so if (this.name !== packet.body.deviceName) { this.settings.set_string('name', packet.body.deviceName); this.notify('name'); } // Packets const incoming = packet.body.incomingCapabilities.sort(); const outgoing = packet.body.outgoingCapabilities.sort(); const inc = this.settings.get_strv('incoming-capabilities'); const out = this.settings.get_strv('outgoing-capabilities'); // Only write GSettings if something has changed if (incoming.join('') !== inc.join('') || outgoing.join('') !== out.join('')) { this.settings.set_strv('incoming-capabilities', incoming); this.settings.set_strv('outgoing-capabilities', outgoing); } // Determine supported plugins by matching incoming to outgoing types const supported = []; for (const name in plugins) { const meta = plugins[name].Metadata; if (meta === undefined) continue; // If we can handle packets it sends or send packets it can handle if (meta.incomingCapabilities.some(t => outgoing.includes(t)) || meta.outgoingCapabilities.some(t => incoming.includes(t))) supported.push(name); } // Only write GSettings if something has changed const currentSupported = this.settings.get_strv('supported-plugins'); if (currentSupported.join('') !== supported.sort().join('')) this.settings.set_strv('supported-plugins', supported); this.thaw_notify(); } /** * Set the channel and start sending/receiving packets. If %null is passed * the device becomes disconnected. * * @param {Core.Channel} [channel] - The new channel */ setChannel(channel = null) { if (this.channel === channel) return; if (this.channel !== null) this.channel.close(); this._channel = channel; // If we've disconnected empty the queue, otherwise restart the read // loop and update the device metadata if (this.channel === null) { this._outputQueue.length = 0; } else { this._handleIdentity(this.channel.identity); this._readLoop(channel); } // The connected state didn't change if (this.connected === !!this.channel) return; // Notify and trigger plugins this._connected = !!this.channel; this.notify('connected'); this._triggerPlugins(); } async _readLoop(channel) { try { let packet = null; while ((packet = await this.channel.readPacket())) { debug(packet, this.name); this.handlePacket(packet); } } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) debug(e, this.name); if (this.channel === channel) this.setChannel(null); } } _processExit(proc, result) { try { proc.wait_check_finish(result); } catch (e) { debug(e); } this.delete(proc); } /** * Launch a subprocess for the device. If the device becomes unpaired, it is * assumed the device is no longer trusted and all subprocesses will be * killed. * * @param {string[]} args - process arguments * @param {Gio.Cancellable} [cancellable] - optional cancellable * @return {Gio.Subprocess} The subprocess */ launchProcess(args, cancellable = null) { if (this._launcher === undefined) { const application = GLib.build_filenamev([ Config.PACKAGE_DATADIR, 'service', 'daemon.js', ]); this._launcher = new Gio.SubprocessLauncher(); this._launcher.setenv('GSCONNECT', application, false); this._launcher.setenv('GSCONNECT_DEVICE_ID', this.id, false); this._launcher.setenv('GSCONNECT_DEVICE_NAME', this.name, false); this._launcher.setenv('GSCONNECT_DEVICE_ICON', this.icon_name, false); this._launcher.setenv( 'GSCONNECT_DEVICE_DBUS', `${Config.APP_PATH}/Device/${this.id.replace(/\W+/g, '_')}`, false ); } // Create and track the process const proc = this._launcher.spawnv(args); proc.wait_check_async(cancellable, this._processExit.bind(this._procs)); this._procs.add(proc); return proc; } /** * Handle a packet and pass it to the appropriate plugin. * * @param {Core.Packet} packet - The incoming packet object * @return {undefined} no return value */ handlePacket(packet) { try { if (packet.type === 'kdeconnect.pair') return this._handlePair(packet); // The device must think we're paired; inform it we are not if (!this.paired) return this.unpair(); const handler = this._handlers.get(packet.type); if (handler !== undefined) handler.handlePacket(packet); else debug(`Unsupported packet type (${packet.type})`, this.name); } catch (e) { debug(e, this.name); } } /** * Send a packet to the device. * * @param {Object} packet - An object of packet data... */ async sendPacket(packet) { try { if (!this.connected) return; if (!this.paired && packet.type !== 'kdeconnect.pair') return; this._outputQueue.push(new Core.Packet(packet)); if (this._outputLock) return; this._outputLock = true; let next; while ((next = this._outputQueue.shift())) { await this.channel.sendPacket(next); debug(next, this.name); } this._outputLock = false; } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) debug(e, this.name); this._outputLock = false; } } /** * Actions */ _registerActions() { // Pairing notification actions const acceptPair = new Gio.SimpleAction({name: 'pair'}); acceptPair.connect('activate', this.pair.bind(this)); this.add_action(acceptPair); const rejectPair = new Gio.SimpleAction({name: 'unpair'}); rejectPair.connect('activate', this.unpair.bind(this)); this.add_action(rejectPair); // Transfer notification actions const cancelTransfer = new Gio.SimpleAction({ name: 'cancelTransfer', parameter_type: new GLib.VariantType('s'), }); cancelTransfer.connect('activate', this.cancelTransfer.bind(this)); this.add_action(cancelTransfer); const openPath = new Gio.SimpleAction({ name: 'openPath', parameter_type: new GLib.VariantType('s'), }); openPath.connect('activate', this.openPath); this.add_action(openPath); const showPathInFolder = new Gio.SimpleAction({ name: 'showPathInFolder', parameter_type: new GLib.VariantType('s'), }); showPathInFolder.connect('activate', this.showPathInFolder); this.add_action(showPathInFolder); // Preference helpers const clearCache = new Gio.SimpleAction({ name: 'clearCache', parameter_type: null, }); clearCache.connect('activate', this._clearCache.bind(this)); this.add_action(clearCache); } /** * Get the position of a GMenuItem with @actionName in the top level of the * device menu. * * @param {string} actionName - An action name with scope (eg. device.foo) * @return {number} An 0-based index or -1 if not found */ getMenuAction(actionName) { for (let i = 0, len = this.menu.get_n_items(); i < len; i++) { try { const val = this.menu.get_item_attribute_value(i, 'action', null); if (val.unpack() === actionName) return i; } catch (e) { continue; } } return -1; } /** * Add a GMenuItem to the top level of the device menu * * @param {Gio.MenuItem} menuItem - A GMenuItem * @param {number} [index] - The position to place the item * @return {number} The position the item was placed */ addMenuItem(menuItem, index = -1) { try { if (index > -1) { this.menu.insert_item(index, menuItem); return index; } this.menu.append_item(menuItem); return this.menu.get_n_items(); } catch (e) { debug(e, this.name); return -1; } } /** * Add a Device GAction to the top level of the device menu * * @param {Gio.Action} action - A GAction * @param {number} [index] - The position to place the item * @param {string} label - A label for the item * @param {string} icon_name - A themed icon name for the item * @return {number} The position the item was placed */ addMenuAction(action, index = -1, label, icon_name) { try { const item = new Gio.MenuItem(); if (label) item.set_label(label); if (icon_name) item.set_icon(new Gio.ThemedIcon({name: icon_name})); item.set_attribute_value( 'hidden-when', new GLib.Variant('s', 'action-disabled') ); item.set_detailed_action(`device.${action.name}`); return this.addMenuItem(item, index); } catch (e) { debug(e, this.name); return -1; } } /** * Remove a GAction from the top level of the device menu by action name * * @param {string} actionName - A GAction name, including scope * @return {number} The position the item was removed from or -1 */ removeMenuAction(actionName) { try { const index = this.getMenuAction(actionName); if (index > -1) this.menu.remove(index); return index; } catch (e) { debug(e, this.name); return -1; } } /** * Withdraw a device notification. * * @param {string} id - Id for the notification to withdraw */ hideNotification(id) { if (this.service === null) return; this.service.withdraw_notification(`${this.id}|${id}`); } /** * Show a device notification. * * @param {Object} params - A dictionary of notification parameters * @param {number} [params.id] - A UNIX epoch timestamp (ms) * @param {string} [params.title] - A title * @param {string} [params.body] - A body * @param {Gio.Icon} [params.icon] - An icon * @param {Gio.NotificationPriority} [params.priority] - The priority * @param {Array} [params.actions] - A dictionary of action parameters * @param {Array} [params.buttons] - An Array of buttons */ showNotification(params) { if (this.service === null) return; // KDE Connect on Android can sometimes give an undefined for params.body Object.keys(params) .forEach(key => params[key] === undefined && delete params[key]); params = Object.assign({ id: Date.now(), title: this.name, body: '', icon: new Gio.ThemedIcon({name: this.icon_name}), priority: Gio.NotificationPriority.NORMAL, action: null, buttons: [], }, params); const notif = new Gio.Notification(); notif.set_title(params.title); notif.set_body(params.body); notif.set_icon(params.icon); notif.set_priority(params.priority); // Default Action if (params.action) { const hasParameter = (params.action.parameter !== null); if (!hasParameter) params.action.parameter = new GLib.Variant('s', ''); notif.set_default_action_and_target( 'app.device', new GLib.Variant('(ssbv)', [ this.id, params.action.name, hasParameter, params.action.parameter, ]) ); } // Buttons for (const button of params.buttons) { const hasParameter = (button.parameter !== null); if (!hasParameter) button.parameter = new GLib.Variant('s', ''); notif.add_button_with_target( button.label, 'app.device', new GLib.Variant('(ssbv)', [ this.id, button.action, hasParameter, button.parameter, ]) ); } this.service.send_notification(`${this.id}|${params.id}`, notif); } /** * Cancel an ongoing file transfer. * * @param {Gio.Action} action - The GAction * @param {GLib.Variant} parameter - The activation parameter */ cancelTransfer(action, parameter) { try { const uuid = parameter.unpack(); const transfer = this._transfers.get(uuid); if (transfer === undefined) return; this._transfers.delete(uuid); transfer.cancel(); } catch (e) { logError(e, this.name); } } /** * Create a transfer object. * * @return {Core.Transfer} A new transfer */ createTransfer() { const transfer = new Core.Transfer({device: this}); // Track the transfer this._transfers.set(transfer.uuid, transfer); transfer.connect('notify::completed', (transfer) => { this._transfers.delete(transfer.uuid); }); return transfer; } /** * Reject the transfer payload described by @packet. * * @param {Core.Packet} packet - A packet * @return {Promise} A promise for the operation */ rejectTransfer(packet) { if (!packet || !packet.hasPayload()) return; return this.channel.rejectTransfer(packet); } openPath(action, parameter) { const path = parameter.unpack(); // Normalize paths to URIs, assuming local file const uri = path.includes('://') ? path : `file://${path}`; Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null); } showPathInFolder(action, parameter) { const path = parameter.unpack(); const uri = path.includes('://') ? path : `file://${path}`; const connection = Gio.DBus.session; connection.call( 'org.freedesktop.FileManager1', '/org/freedesktop/FileManager1', 'org.freedesktop.FileManager1', 'ShowItems', new GLib.Variant('(ass)', [[uri], 's']), null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { connection.call_finish(res); } catch (e) { Gio.DBusError.strip_remote_error(e); logError(e); } } ); } _clearCache(action, parameter) { for (const plugin of this._plugins.values()) { try { plugin.clearCache(); } catch (e) { debug(e, this.name); } } } /** * Pair request handler * * @param {Core.Packet} packet - A complete kdeconnect.pair packet */ _handlePair(packet) { // A pair has been requested/confirmed if (packet.body.pair) { // The device is accepting our request if (this._outgoingPairRequest) { this._setPaired(true); this._loadPlugins(); // The device thinks we're unpaired } else if (this.paired) { this._setPaired(true); this.sendPacket({ type: 'kdeconnect.pair', body: {pair: true}, }); this._triggerPlugins(); // The device is requesting pairing } else { this._notifyPairRequest(); } // Device is requesting unpairing/rejecting our request } else { this._setPaired(false); this._unloadPlugins(); } } /** * Notify the user of an incoming pair request and set a 30s timeout */ _notifyPairRequest() { // Reset any active request this._resetPairRequest(); this.showNotification({ id: 'pair-request', // TRANSLATORS: eg. Pair Request from Google Pixel title: _('Pair Request from %s').format(this.name), body: this.encryption_info, icon: new Gio.ThemedIcon({name: 'channel-insecure-symbolic'}), priority: Gio.NotificationPriority.URGENT, buttons: [ { action: 'unpair', label: _('Reject'), parameter: null, }, { action: 'pair', label: _('Accept'), parameter: null, }, ], }); // Start a 30s countdown this._incomingPairRequest = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT, 30, this._setPaired.bind(this, false) ); } /** * Reset pair request timeouts and withdraw any notifications */ _resetPairRequest() { this.hideNotification('pair-request'); if (this._incomingPairRequest) { GLib.source_remove(this._incomingPairRequest); this._incomingPairRequest = 0; } if (this._outgoingPairRequest) { GLib.source_remove(this._outgoingPairRequest); this._outgoingPairRequest = 0; } } /** * Set the internal paired state of the device and emit ::notify * * @param {boolean} paired - The paired state to set */ _setPaired(paired) { this._resetPairRequest(); // For TCP connections we store or reset the TLS Certificate if (this.connection_type === 'lan') { if (paired) { this.settings.set_string( 'certificate-pem', this.channel.peer_certificate.certificate_pem ); } else { this.settings.reset('certificate-pem'); } } // If we've become unpaired, stop all subprocesses and transfers if (!paired) { for (const proc of this._procs) proc.force_exit(); this._procs.clear(); for (const transfer of this._transfers.values()) transfer.close(); this._transfers.clear(); } this.settings.set_boolean('paired', paired); this.notify('paired'); } /** * Send or accept an incoming pair request; also exported as a GAction */ pair() { try { // If we're accepting an incoming pair request, set the internal // paired state and send the confirmation before loading plugins. if (this._incomingPairRequest) { this._setPaired(true); this.sendPacket({ type: 'kdeconnect.pair', body: {pair: true}, }); this._loadPlugins(); // If we're initiating an outgoing pair request, be sure the timer // is reset before sending the request and setting a 30s timeout. } else if (!this.paired) { this._resetPairRequest(); this.sendPacket({ type: 'kdeconnect.pair', body: {pair: true}, }); this._outgoingPairRequest = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT, 30, this._setPaired.bind(this, false) ); } } catch (e) { logError(e, this.name); } } /** * Unpair or reject an incoming pair request; also exported as a GAction */ unpair() { try { if (this.connected) { this.sendPacket({ type: 'kdeconnect.pair', body: {pair: false}, }); } this._setPaired(false); this._unloadPlugins(); } catch (e) { logError(e, this.name); } } /* * Plugin Functions */ _onAllowedPluginsChanged(settings) { const disabled = this.settings.get_strv('disabled-plugins'); const supported = this.settings.get_strv('supported-plugins'); const allowed = supported.filter(name => !disabled.includes(name)); // Unload any plugins that are disabled or unsupported this._plugins.forEach(plugin => { if (!allowed.includes(plugin.name)) this._unloadPlugin(plugin.name); }); // Make sure we change the contacts store if the plugin was disabled if (!allowed.includes('contacts')) this.notify('contacts'); // Load allowed plugins for (const name of allowed) this._loadPlugin(name); } _loadPlugin(name) { let handler, plugin; try { if (this.paired && !this._plugins.has(name)) { // Instantiate the handler handler = plugins[name]; plugin = new handler.default(this); // Register packet handlers for (const packetType of handler.Metadata.incomingCapabilities) this._handlers.set(packetType, plugin); // Register plugin this._plugins.set(name, plugin); // Run the connected()/disconnected() handler if (this.connected) plugin.connected(); else plugin.disconnected(); } } catch (e) { if (plugin !== undefined) plugin.destroy(); if (this.service !== null) this.service.notify_error(e); else logError(e, this.name); } } async _loadPlugins() { const disabled = this.settings.get_strv('disabled-plugins'); for (const name of this.settings.get_strv('supported-plugins')) { if (!disabled.includes(name)) await this._loadPlugin(name); } } _unloadPlugin(name) { let handler, plugin; try { if (this._plugins.has(name)) { // Unregister packet handlers handler = plugins[name]; for (const type of handler.Metadata.incomingCapabilities) this._handlers.delete(type); // Unregister plugin plugin = this._plugins.get(name); this._plugins.delete(name); plugin.destroy(); } } catch (e) { logError(e, this.name); } } async _unloadPlugins() { for (const name of this._plugins.keys()) await this._unloadPlugin(name); } _triggerPlugins() { for (const plugin of this._plugins.values()) { if (this.connected) plugin.connected(); else plugin.disconnected(); } } destroy() { // Drop the default contacts store if we were using it if (this._contacts !== undefined) this._contacts = Components.release('contacts'); // Close the channel if still connected if (this.channel !== null) this.channel.close(); // Synchronously destroy plugins this._plugins.forEach(plugin => plugin.destroy()); this._plugins.clear(); // Dispose GSettings this.settings.disconnect(this._disabledPluginsChangedId); this.settings.disconnect(this._supportedPluginsChangedId); this.settings.run_dispose(); GObject.signal_handlers_destroy(this); } }); export default Device; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/init.js�������������������������������0000664�0000000�0000000�00000033443�14607666711�0025603�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import {watchService} from '../wl_clipboard.js'; import Gio from 'gi://Gio'; import GIRepository from 'gi://GIRepository'; import GLib from 'gi://GLib'; import Config from '../config.js'; import setup, {setupGettext} from '../utils/setup.js'; // Promise Wrappers // We don't use top-level await since it returns control flow to importing module, causing bugs import('gi://EBook').then(({default: EBook}) => { Gio._promisify(EBook.BookClient, 'connect'); Gio._promisify(EBook.BookClient.prototype, 'get_view'); Gio._promisify(EBook.BookClient.prototype, 'get_contacts'); }).catch(console.debug); import('gi://EDataServer').then(({default: EDataServer}) => { Gio._promisify(EDataServer.SourceRegistry, 'new'); }).catch(console.debug); Gio._promisify(Gio.AsyncInitable.prototype, 'init_async'); Gio._promisify(Gio.DBusConnection.prototype, 'call'); Gio._promisify(Gio.DBusProxy.prototype, 'call'); Gio._promisify(Gio.DataInputStream.prototype, 'read_line_async', 'read_line_finish_utf8'); Gio._promisify(Gio.File.prototype, 'delete_async'); Gio._promisify(Gio.File.prototype, 'enumerate_children_async'); Gio._promisify(Gio.File.prototype, 'load_contents_async'); Gio._promisify(Gio.File.prototype, 'mount_enclosing_volume'); Gio._promisify(Gio.File.prototype, 'query_info_async'); Gio._promisify(Gio.File.prototype, 'read_async'); Gio._promisify(Gio.File.prototype, 'replace_async'); Gio._promisify(Gio.File.prototype, 'replace_contents_bytes_async', 'replace_contents_finish'); Gio._promisify(Gio.FileEnumerator.prototype, 'next_files_async'); Gio._promisify(Gio.Mount.prototype, 'unmount_with_operation'); Gio._promisify(Gio.InputStream.prototype, 'close_async'); Gio._promisify(Gio.OutputStream.prototype, 'close_async'); Gio._promisify(Gio.OutputStream.prototype, 'splice_async'); Gio._promisify(Gio.OutputStream.prototype, 'write_all_async'); Gio._promisify(Gio.SocketClient.prototype, 'connect_async'); Gio._promisify(Gio.SocketListener.prototype, 'accept_async'); Gio._promisify(Gio.Subprocess.prototype, 'communicate_utf8_async'); Gio._promisify(Gio.Subprocess.prototype, 'wait_check_async'); Gio._promisify(Gio.TlsConnection.prototype, 'handshake_async'); Gio._promisify(Gio.DtlsConnection.prototype, 'handshake_async'); // User Directories Config.CACHEDIR = GLib.build_filenamev([GLib.get_user_cache_dir(), 'gsconnect']); Config.CONFIGDIR = GLib.build_filenamev([GLib.get_user_config_dir(), 'gsconnect']); Config.RUNTIMEDIR = GLib.build_filenamev([GLib.get_user_runtime_dir(), 'gsconnect']); // Bootstrap const serviceFolder = GLib.path_get_dirname(GLib.filename_from_uri(import.meta.url)[0]); const extensionFolder = GLib.path_get_dirname(serviceFolder); setup(extensionFolder); setupGettext(); if (Config.IS_USER) { // Infer libdir by assuming gnome-shell shares a common prefix with gjs; // assume the parent directory if it's not there let libdir = GIRepository.Repository.get_search_path().find(path => { return path.endsWith('/gjs/girepository-1.0'); }).replace('/gjs/girepository-1.0', ''); const gsdir = GLib.build_filenamev([libdir, 'gnome-shell']); if (!GLib.file_test(gsdir, GLib.FileTest.IS_DIR)) { const currentDir = `/${GLib.path_get_basename(libdir)}`; libdir = libdir.replace(currentDir, ''); } Config.GNOME_SHELL_LIBDIR = libdir; } // Load DBus interfaces Config.DBUS = (() => { const bytes = Gio.resources_lookup_data( GLib.build_filenamev([Config.APP_PATH, `${Config.APP_ID}.xml`]), Gio.ResourceLookupFlags.NONE ); const xml = new TextDecoder().decode(bytes.toArray()); const dbus = Gio.DBusNodeInfo.new_for_xml(xml); dbus.nodes.forEach(info => info.cache_build()); return dbus; })(); // Init User Directories for (const path of [Config.CACHEDIR, Config.CONFIGDIR, Config.RUNTIMEDIR]) GLib.mkdir_with_parents(path, 0o755); globalThis.HAVE_GNOME = GLib.getenv('GSCONNECT_MODE')?.toLowerCase() !== 'cli' && (GLib.getenv('GNOME_SETUP_DISPLAY') !== null || GLib.getenv('XDG_CURRENT_DESKTOP')?.toUpperCase()?.includes('GNOME') || GLib.getenv('XDG_SESSION_DESKTOP')?.toLowerCase() === 'gnome'); /** * A custom debug function that logs at LEVEL_MESSAGE to avoid the need for env * variables to be set. * * @param {Error|string} message - A string or Error to log * @param {string} [prefix] - An optional prefix for the warning */ const _debugCallerMatch = new RegExp(/([^@]*)@([^:]*):([^:]*)/); // eslint-disable-next-line func-style const _debugFunc = function (error, prefix = null) { let caller, message; if (error.stack) { caller = error.stack.split('\n')[0]; message = `${error.message}\n${error.stack}`; } else { caller = (new Error()).stack.split('\n')[1]; message = JSON.stringify(error, null, 2); } if (prefix) message = `${prefix}: ${message}`; const [, func, file, line] = _debugCallerMatch.exec(caller); const script = file.replace(Config.PACKAGE_DATADIR, ''); GLib.log_structured('GSConnect', GLib.LogLevelFlags.LEVEL_MESSAGE, { 'MESSAGE': `[${script}:${func}:${line}]: ${message}`, 'SYSLOG_IDENTIFIER': 'org.gnome.Shell.Extensions.GSConnect', 'CODE_FILE': file, 'CODE_FUNC': func, 'CODE_LINE': line, }); }; // Swap the function out for a no-op anonymous function for speed const settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup(Config.APP_ID, true), }); settings.connect('changed::debug', (settings, key) => { globalThis.debug = settings.get_boolean(key) ? _debugFunc : () => {}; }); if (settings.get_boolean('debug')) globalThis.debug = _debugFunc; else globalThis.debug = () => {}; /** * Start wl_clipboard if not under Gnome */ if (!globalThis.HAVE_GNOME) { debug('Not running as a Gnome extension'); watchService(); } /** * A simple (for now) pre-comparison sanitizer for phone numbers * See: https://github.com/KDE/kdeconnect-kde/blob/master/smsapp/conversationlistmodel.cpp#L200-L210 * * @return {string} Return the string stripped of leading 0, and ' ()-+' */ String.prototype.toPhoneNumber = function () { const strippedNumber = this.replace(/^0*|[ ()+-]/g, ''); if (strippedNumber.length) return strippedNumber; return this; }; /** * A simple equality check for phone numbers based on `toPhoneNumber()` * * @param {string} number - A phone number string to compare * @return {boolean} If `this` and @number are equivalent phone numbers */ String.prototype.equalsPhoneNumber = function (number) { const a = this.toPhoneNumber(); const b = number.toPhoneNumber(); return (a.length && b.length && (a.endsWith(b) || b.endsWith(a))); }; /** * An implementation of `rm -rf` in Gio * * @param {Gio.File|string} file - a GFile or filepath */ Gio.File.rm_rf = function (file) { try { if (typeof file === 'string') file = Gio.File.new_for_path(file); try { const iter = file.enumerate_children( 'standard::name', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null ); let info; while ((info = iter.next_file(null))) Gio.File.rm_rf(iter.get_child(info)); iter.close(null); } catch (e) { // Silence errors } file.delete(null); } catch (e) { // Silence errors } }; /** * Extend GLib.Variant with a static method to recursively pack a variant * * @param {*} [obj] - May be a GLib.Variant, Array, standard Object or literal. * @return {GLib.Variant} The resulting GVariant */ function _full_pack(obj) { let packed; const type = typeof obj; switch (true) { case (obj instanceof GLib.Variant): return obj; case (type === 'string'): return GLib.Variant.new('s', obj); case (type === 'number'): return GLib.Variant.new('d', obj); case (type === 'boolean'): return GLib.Variant.new('b', obj); case (obj instanceof Uint8Array): return GLib.Variant.new('ay', obj); case (obj === null): return GLib.Variant.new('mv', null); case (typeof obj.map === 'function'): return GLib.Variant.new( 'av', obj.filter(e => e !== undefined).map(e => _full_pack(e)) ); case (obj instanceof Gio.Icon): return obj.serialize(); case (type === 'object'): packed = {}; for (const [key, val] of Object.entries(obj)) { if (val !== undefined) packed[key] = _full_pack(val); } return GLib.Variant.new('a{sv}', packed); default: throw Error(`Unsupported type '${type}': ${obj}`); } } GLib.Variant.full_pack = _full_pack; /** * Extend GLib.Variant with a method to recursively deepUnpack() a variant * * @param {*} [obj] - May be a GLib.Variant, Array, standard Object or literal. * @return {*} The resulting object */ function _full_unpack(obj) { obj = (obj === undefined) ? this : obj; const unpacked = {}; switch (true) { case (obj === null): return obj; case (obj instanceof GLib.Variant): return _full_unpack(obj.deepUnpack()); case (obj instanceof Uint8Array): return obj; case (typeof obj.map === 'function'): return obj.map(e => _full_unpack(e)); case (typeof obj === 'object'): for (const [key, value] of Object.entries(obj)) { // Try to detect and deserialize GIcons try { if (key === 'icon' && value.get_type_string() === '(sv)') unpacked[key] = Gio.Icon.deserialize(value); else unpacked[key] = _full_unpack(value); } catch (e) { unpacked[key] = _full_unpack(value); } } return unpacked; default: return obj; } } GLib.Variant.prototype.full_unpack = _full_unpack; /** * Creates a GTlsCertificate from the PEM-encoded data in @cert_path and * @key_path. If either are missing a new pair will be generated. * * Additionally, the private key will be added using ssh-add to allow sftp * connections using Gio. * * See: https://github.com/KDE/kdeconnect-kde/blob/master/core/kdeconnectconfig.cpp#L119 * * @param {string} certPath - Absolute path to a x509 certificate in PEM format * @param {string} keyPath - Absolute path to a private key in PEM format * @param {string} commonName - A unique common name for the certificate * @return {Gio.TlsCertificate} A TLS certificate */ Gio.TlsCertificate.new_for_paths = function (certPath, keyPath, commonName = null) { // Check if the certificate/key pair already exists const certExists = GLib.file_test(certPath, GLib.FileTest.EXISTS); const keyExists = GLib.file_test(keyPath, GLib.FileTest.EXISTS); // Create a new certificate and private key if necessary if (!certExists || !keyExists) { // If we weren't passed a common name, generate a random one if (!commonName) commonName = GLib.uuid_string_random(); const proc = new Gio.Subprocess({ argv: [ Config.OPENSSL_PATH, 'req', '-new', '-x509', '-sha256', '-out', certPath, '-newkey', 'rsa:4096', '-nodes', '-keyout', keyPath, '-days', '3650', '-subj', `/O=andyholmes.github.io/OU=GSConnect/CN=${commonName}`, ], flags: (Gio.SubprocessFlags.STDOUT_SILENCE | Gio.SubprocessFlags.STDERR_SILENCE), }); proc.init(null); proc.wait_check(null); } return Gio.TlsCertificate.new_from_files(certPath, keyPath); }; Object.defineProperties(Gio.TlsCertificate.prototype, { /** * The common name of the certificate. */ 'common_name': { get: function () { if (!this.__common_name) { const proc = new Gio.Subprocess({ argv: [Config.OPENSSL_PATH, 'x509', '-noout', '-subject', '-inform', 'pem'], flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE, }); proc.init(null); const stdout = proc.communicate_utf8(this.certificate_pem, null)[1]; this.__common_name = /(?:cn|CN) ?= ?([^,\n]*)/.exec(stdout)[1]; } return this.__common_name; }, configurable: true, enumerable: true, }, /** * Get just the pubkey as a DER ByteArray of a certificate. * * @return {GLib.Bytes} The pubkey as DER of the certificate. */ 'pubkey_der': { value: function () { if (!this.__pubkey_der) { let proc = new Gio.Subprocess({ argv: [Config.OPENSSL_PATH, 'x509', '-noout', '-pubkey', '-inform', 'pem'], flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE, }); proc.init(null); const pubkey = proc.communicate_utf8(this.certificate_pem, null)[1]; proc = new Gio.Subprocess({ argv: [Config.OPENSSL_PATH, 'pkey', '-pubin', '-inform', 'pem', '-outform', 'der'], flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE, }); proc.init(null); this.__pubkey_der = proc.communicate(new TextEncoder().encode(pubkey), null)[1]; } return this.__pubkey_der; }, configurable: true, enumerable: false, }, }); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/manager.js����������������������������0000664�0000000�0000000�00000033362�14607666711�0026252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Config from '../config.js'; import * as DBus from './utils/dbus.js'; import Device from './device.js'; import * as LanBackend from './backends/lan.js'; const DEVICE_NAME = 'org.gnome.Shell.Extensions.GSConnect.Device'; const DEVICE_PATH = '/org/gnome/Shell/Extensions/GSConnect/Device'; const DEVICE_IFACE = Config.DBUS.lookup_interface(DEVICE_NAME); const backends = { lan: LanBackend, }; /** * A manager for devices. */ const Manager = GObject.registerClass({ GTypeName: 'GSConnectManager', Properties: { 'active': GObject.ParamSpec.boolean( 'active', 'Active', 'Whether the manager is active', GObject.ParamFlags.READABLE, false ), 'discoverable': GObject.ParamSpec.boolean( 'discoverable', 'Discoverable', 'Whether the service responds to discovery requests', GObject.ParamFlags.READWRITE, false ), 'id': GObject.ParamSpec.string( 'id', 'Id', 'The hostname or other network unique id', GObject.ParamFlags.READWRITE, null ), 'name': GObject.ParamSpec.string( 'name', 'Name', 'The name announced to the network', GObject.ParamFlags.READWRITE, 'GSConnect' ), }, }, class Manager extends Gio.DBusObjectManagerServer { _init(params = {}) { super._init(params); this._exported = new WeakMap(); this._reconnectId = 0; this._settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup(Config.APP_ID, true), }); this._initSettings(); } get active() { if (this._active === undefined) this._active = false; return this._active; } get backends() { if (this._backends === undefined) this._backends = new Map(); return this._backends; } get devices() { if (this._devices === undefined) this._devices = new Map(); return this._devices; } get discoverable() { if (this._discoverable === undefined) this._discoverable = this.settings.get_boolean('discoverable'); return this._discoverable; } set discoverable(value) { if (this.discoverable === value) return; this._discoverable = value; this.notify('discoverable'); // FIXME: This whole thing just keeps getting uglier const application = Gio.Application.get_default(); if (application === null) return; if (this.discoverable) { Gio.Application.prototype.withdraw_notification.call( application, 'discovery-warning' ); } else { const notif = new Gio.Notification(); notif.set_title(_('Discovery Disabled')); notif.set_body(_('Discovery has been disabled due to the number of devices on this network.')); notif.set_icon(new Gio.ThemedIcon({name: 'dialog-warning'})); notif.set_priority(Gio.NotificationPriority.HIGH); notif.set_default_action('app.preferences'); Gio.Application.prototype.withdraw_notification.call( application, 'discovery-warning', notif ); } } get id() { if (this._id === undefined) this._id = this.settings.get_string('id'); return this._id; } set id(value) { if (this.id === value) return; this._id = value; this.notify('id'); } get name() { if (this._name === undefined) this._name = this.settings.get_string('name'); return this._name; } set name(value) { if (this.name === value) return; this._name = value; this.notify('name'); // Broadcast changes to the network for (const backend of this.backends.values()) { backend.name = this.name; backend.buildIdentity(); } this.identify(); } get settings() { if (this._settings === undefined) { this._settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup(Config.APP_ID, true), }); } return this._settings; } vfunc_notify(pspec) { if (pspec.name !== 'connection') return; if (this.connection !== null) this._exportDevices(); else this._unexportDevices(); } /* * GSettings */ _initSettings() { // Initialize the ID and name of the service if (this.settings.get_string('id').length === 0) this.settings.set_string('id', GLib.uuid_string_random()); if (this.settings.get_string('name').length === 0) this.settings.set_string('name', GLib.get_host_name()); // Bound Properties this.settings.bind('discoverable', this, 'discoverable', 0); this.settings.bind('id', this, 'id', 0); this.settings.bind('name', this, 'name', 0); } /* * Backends */ _onChannel(backend, channel) { try { let device = this.devices.get(channel.identity.body.deviceId); switch (true) { // Proceed if this is an existing device... case (device !== undefined): break; // Or the connection is allowed... case this.discoverable || channel.allowed: device = this._ensureDevice(channel.identity); break; // ...otherwise bail default: debug(`${channel.identity.body.deviceName}: not allowed`); return false; } device.setChannel(channel); return true; } catch (e) { logError(e, backend.name); return false; } } _loadBackends() { for (const name in backends) { try { const module = backends[name]; if (module.ChannelService === undefined) continue; // Try to create the backend and track it if successful const backend = new module.ChannelService({ id: this.id, name: this.name, }); this.backends.set(name, backend); // Connect to the backend backend.__channelId = backend.connect( 'channel', this._onChannel.bind(this) ); // Now try to start the backend, allowing us to retry if we fail backend.start(); } catch (e) { if (Gio.Application.get_default()) Gio.Application.get_default().notify_error(e); } } } /* * Devices */ _loadDevices() { // Load cached devices for (const id of this.settings.get_strv('devices')) { const device = new Device({body: {deviceId: id}}); this._exportDevice(device); this.devices.set(id, device); } } _exportDevice(device) { if (this.connection === null) return; const info = { object: null, interface: null, actions: 0, menu: 0, }; const objectPath = `${DEVICE_PATH}/${device.id.replace(/\W+/g, '_')}`; // Export an object path for the device info.object = new Gio.DBusObjectSkeleton({ g_object_path: objectPath, }); this.export(info.object); // Export GActions & GMenu info.actions = Gio.DBus.session.export_action_group(objectPath, device); info.menu = Gio.DBus.session.export_menu_model(objectPath, device.menu); // Export the Device interface info.interface = new DBus.Interface({ g_instance: device, g_interface_info: DEVICE_IFACE, }); info.object.add_interface(info.interface); this._exported.set(device, info); } _exportDevices() { if (this.connection === null) return; for (const device of this.devices.values()) this._exportDevice(device); } _unexportDevice(device) { const info = this._exported.get(device); if (info === undefined) return; // Unexport GActions and GMenu Gio.DBus.session.unexport_action_group(info.actions); Gio.DBus.session.unexport_menu_model(info.menu); // Unexport the Device interface and object info.interface.flush(); info.object.remove_interface(info.interface); info.object.flush(); this.unexport(info.object.g_object_path); this._exported.delete(device); } _unexportDevices() { for (const device of this.devices.values()) this._unexportDevice(device); } /** * Return a device for @packet, creating it and adding it to the list of * of known devices if it doesn't exist. * * @param {Core.Packet} packet - An identity packet for the device * @return {Device} A device object */ _ensureDevice(packet) { let device = this.devices.get(packet.body.deviceId); if (device === undefined) { debug(`Adding ${packet.body.deviceName}`); // TODO: Remove when all clients support bluetooth-like discovery // // If this is the third unpaired device to connect, we disable // discovery to avoid choking on networks with many devices const unpaired = Array.from(this.devices.values()).filter(dev => { return !dev.paired; }); if (unpaired.length === 3) this.discoverable = false; device = new Device(packet); this._exportDevice(device); this.devices.set(device.id, device); // Notify this.settings.set_strv('devices', Array.from(this.devices.keys())); } return device; } /** * Permanently remove a device. * * Removes the device from the list of known devices, deletes all GSettings * and files. * * @param {string} id - The id of the device to delete */ _removeDevice(id) { // Delete all GSettings const settings_path = `/org/gnome/shell/extensions/gsconnect/${id}/`; GLib.spawn_command_line_async(`dconf reset -f ${settings_path}`); // Delete the cache const cache = GLib.build_filenamev([Config.CACHEDIR, id]); Gio.File.rm_rf(cache); // Forget the device this.devices.delete(id); this.settings.set_strv('devices', Array.from(this.devices.keys())); } /** * A GSourceFunc that tries to reconnect to each paired device, while * pruning unpaired devices that have disconnected. * * @return {boolean} Always %true */ _reconnect() { for (const [id, device] of this.devices) { if (device.connected) continue; if (device.paired) { this.identify(device.settings.get_string('last-connection')); continue; } this._unexportDevice(device); this._removeDevice(id); device.destroy(); } return GLib.SOURCE_CONTINUE; } /** * Identify to an address or broadcast to the network. * * @param {string} [uri] - An address URI or %null to broadcast */ identify(uri = null) { try { // If we're passed a parameter, try and find a backend for it if (uri !== null) { const [scheme, address] = uri.split('://'); const backend = this.backends.get(scheme); if (backend !== undefined) backend.broadcast(address); // If we're not discoverable, only try to reconnect known devices } else if (!this.discoverable) { this._reconnect(); // Otherwise have each backend broadcast to it's network } else { this.backends.forEach(backend => backend.broadcast()); } } catch (e) { logError(e); } } /** * Start managing devices. */ start() { if (this.active) return; this._loadDevices(); this._loadBackends(); if (this._reconnectId === 0) { this._reconnectId = GLib.timeout_add_seconds( GLib.PRIORITY_LOW, 5, this._reconnect.bind(this) ); } this._active = true; this.notify('active'); } /** * Stop managing devices. */ stop() { if (!this.active) return; if (this._reconnectId > 0) { GLib.Source.remove(this._reconnectId); this._reconnectId = 0; } this._unexportDevices(); this.backends.forEach(backend => backend.destroy()); this.backends.clear(); this.devices.forEach(device => device.destroy()); this.devices.clear(); this._active = false; this.notify('active'); } /** * Stop managing devices and free any resources. */ destroy() { this.stop(); this.set_connection(null); } }); export default Manager; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/nativeMessagingHost.js����������������0000775�0000000�0000000�00000014653�14607666711�0030627�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env -S gjs -m // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio?version=2.0'; import GLib from 'gi://GLib?version=2.0'; import GObject from 'gi://GObject?version=2.0'; import system from 'system'; // Retain compatibility with GLib < 2.80, which lacks GioUnix let GioUnix; try { GioUnix = (await import('gi://GioUnix?version=2.0')).default; } catch (e) { GioUnix = { InputStream: Gio.UnixInputStream, OutputStream: Gio.UnixOutputStream, }; } const NativeMessagingHost = GObject.registerClass({ GTypeName: 'GSConnectNativeMessagingHost', }, class NativeMessagingHost extends Gio.Application { _init() { super._init({ application_id: 'org.gnome.Shell.Extensions.GSConnect.NativeMessagingHost', flags: Gio.ApplicationFlags.NON_UNIQUE, }); } get devices() { if (this._devices === undefined) this._devices = {}; return this._devices; } vfunc_activate() { super.vfunc_activate(); } vfunc_startup() { super.vfunc_startup(); this.hold(); // IO Channels this._stdin = new Gio.DataInputStream({ base_stream: new GioUnix.InputStream({fd: 0}), byte_order: Gio.DataStreamByteOrder.HOST_ENDIAN, }); this._stdout = new Gio.DataOutputStream({ base_stream: new GioUnix.OutputStream({fd: 1}), byte_order: Gio.DataStreamByteOrder.HOST_ENDIAN, }); const source = this._stdin.base_stream.create_source(null); source.set_callback(this.receive.bind(this)); source.attach(null); // Device Manager try { this._manager = Gio.DBusObjectManagerClient.new_for_bus_sync( Gio.BusType.SESSION, Gio.DBusObjectManagerClientFlags.DO_NOT_AUTO_START, 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', null, null ); } catch (e) { logError(e); this.quit(); } // Add currently managed devices for (const object of this._manager.get_objects()) { for (const iface of object.get_interfaces()) this._onInterfaceAdded(this._manager, object, iface); } // Watch for new and removed devices this._manager.connect( 'interface-added', this._onInterfaceAdded.bind(this) ); this._manager.connect( 'object-removed', this._onObjectRemoved.bind(this) ); // Watch for device property changes this._manager.connect( 'interface-proxy-properties-changed', this.sendDeviceList.bind(this) ); // Watch for service restarts this._manager.connect( 'notify::name-owner', this.sendDeviceList.bind(this) ); this.send({ type: 'connected', data: (this._manager.name_owner !== null), }); } receive() { try { // Read the message const length = this._stdin.read_int32(null); const bytes = this._stdin.read_bytes(length, null).toArray(); const message = JSON.parse(new TextDecoder().decode(bytes)); // A request for a list of devices if (message.type === 'devices') { this.sendDeviceList(); // A request to invoke an action } else if (message.type === 'share') { let actionName; const device = this.devices[message.data.device]; if (device) { if (message.data.action === 'share') actionName = 'shareUri'; else if (message.data.action === 'telephony') actionName = 'shareSms'; device.actions.activate_action( actionName, new GLib.Variant('s', message.data.url) ); } } return GLib.SOURCE_CONTINUE; } catch (e) { this.quit(); } } send(message) { try { const data = JSON.stringify(message); this._stdout.put_int32(data.length, null); this._stdout.put_string(data, null); } catch (e) { this.quit(); } } sendDeviceList() { // Inform the WebExtension we're disconnected from the service if (this._manager && this._manager.name_owner === null) return this.send({type: 'connected', data: false}); // Collect all the devices with supported actions const available = []; for (const device of Object.values(this.devices)) { const share = device.actions.get_action_enabled('shareUri'); const telephony = device.actions.get_action_enabled('shareSms'); if (share || telephony) { available.push({ id: device.g_object_path, name: device.name, type: device.type, share: share, telephony: telephony, }); } } this.send({type: 'devices', data: available}); } _proxyGetter(name) { try { return this.get_cached_property(name).unpack(); } catch (e) { return null; } } _onInterfaceAdded(manager, object, iface) { Object.defineProperties(iface, { 'name': { get: this._proxyGetter.bind(iface, 'Name'), enumerable: true, }, // TODO: phase this out for icon-name 'type': { get: this._proxyGetter.bind(iface, 'Type'), enumerable: true, }, }); iface.actions = Gio.DBusActionGroup.get( iface.g_connection, iface.g_name, iface.g_object_path ); this.devices[iface.g_object_path] = iface; this.sendDeviceList(); } _onObjectRemoved(manager, object) { delete this.devices[object.g_object_path]; this.sendDeviceList(); } }); // NOTE: must not pass ARGV await (new NativeMessagingHost()).runAsync([system.programInvocationName]); �������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugin.js�����������������������������0000664�0000000�0000000�00000016051�14607666711�0026132�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Config from '../config.js'; import plugins from './plugins/index.js'; /** * Base class for device plugins. */ const Plugin = GObject.registerClass({ GTypeName: 'GSConnectPlugin', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device that owns this plugin', GObject.ParamFlags.READABLE, GObject.Object ), 'name': GObject.ParamSpec.string( 'name', 'Name', 'The device name', GObject.ParamFlags.READABLE, null ), }, }, class Plugin extends GObject.Object { _init(device, name, meta = null) { super._init(); this._device = device; this._name = name; this._meta = meta; if (this._meta === null) this._meta = plugins[name].Metadata; // GSettings const schema = Config.GSCHEMA.lookup(this._meta.id, false); if (schema !== null) { this.settings = new Gio.Settings({ settings_schema: schema, path: `${device.settings.path}plugin/${name}/`, }); } // GActions this._gactions = []; if (this._meta.actions) { const menu = this.device.settings.get_strv('menu-actions'); for (const name in this._meta.actions) { const info = this._meta.actions[name]; this._registerAction(name, menu.indexOf(name), info); } } } get cancellable() { if (this._cancellable === undefined) this._cancellable = new Gio.Cancellable(); return this._cancellable; } get device() { return this._device; } get name() { return this._name; } _activateAction(action, parameter) { try { let args = null; if (parameter instanceof GLib.Variant) args = parameter.full_unpack(); if (Array.isArray(args)) this[action.name](...args); else this[action.name](args); } catch (e) { logError(e, action.name); } } _registerAction(name, menuIndex, info) { try { // Device Action const action = new Gio.SimpleAction({ name: name, parameter_type: info.parameter_type, enabled: false, }); action.connect('activate', this._activateAction.bind(this)); this.device.add_action(action); // Menu if (menuIndex > -1) { this.device.addMenuAction( action, menuIndex, info.label, info.icon_name ); } this._gactions.push(action); } catch (e) { logError(e, `${this.device.name}: ${this.name}`); } } /** * Called when the device connects. */ connected() { // Enabled based on device capabilities, which might change const incoming = this.device.settings.get_strv('incoming-capabilities'); const outgoing = this.device.settings.get_strv('outgoing-capabilities'); for (const action of this._gactions) { const info = this._meta.actions[action.name]; if (info.incoming.every(type => outgoing.includes(type)) && info.outgoing.every(type => incoming.includes(type))) action.set_enabled(true); } } /** * Called when the device disconnects. */ disconnected() { for (const action of this._gactions) action.set_enabled(false); } /** * Called when a packet is received that the plugin is a handler for. * * @param {Core.Packet} packet - A KDE Connect packet */ handlePacket(packet) { throw new GObject.NotImplementedError(); } /** * Cache JSON parseable properties on this object for persistence. The * filename ~/.cache/gsconnect/<device-id>/<plugin-name>.json will be used * to store the properties and values. * * Calling cacheProperties() opens a JSON cache file and reads any stored * properties and values onto the current instance. When destroy() * is called the properties are automatically stored in the same file. * * @param {Array} names - A list of this object's property names to cache */ async cacheProperties(names) { try { this._cacheProperties = names; // Ensure the device's cache directory exists const cachedir = GLib.build_filenamev([ Config.CACHEDIR, this.device.id, ]); GLib.mkdir_with_parents(cachedir, 448); this._cacheFile = Gio.File.new_for_path( GLib.build_filenamev([cachedir, `${this.name}.json`])); // Read the cache from disk const [contents] = await this._cacheFile.load_contents_async( this.cancellable); const cache = JSON.parse(new TextDecoder().decode(contents)); Object.assign(this, cache); } catch (e) { debug(e.message, `${this.device.name}: ${this.name}`); } finally { this.cacheLoaded(); } } /** * An overridable function that is invoked when the on-disk cache is being * cleared. Implementations should use this function to clear any in-memory * cache data. */ clearCache() {} /** * An overridable function that is invoked when the cache is done loading */ cacheLoaded() {} /** * Unregister plugin actions, write the cache (if applicable) and destroy * any dangling signal handlers. */ destroy() { // Cancel any pending plugin operations if (this._cancellable !== undefined) this._cancellable.cancel(); for (const action of this._gactions) { this.device.removeMenuAction(`device.${action.name}`); this.device.remove_action(action.name); } // Write the cache to disk synchronously if (this._cacheFile !== undefined) { try { // Build the cache const cache = {}; for (const name of this._cacheProperties) cache[name] = this[name]; this._cacheFile.replace_contents( JSON.stringify(cache, null, 2), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null ); } catch (e) { debug(e.message, `${this.device.name}: ${this.name}`); } } GObject.signal_handlers_destroy(this); } }); export default Plugin; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/������������������������������0000775�0000000�0000000�00000000000�14607666711�0025754�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/battery.js��������������������0000664�0000000�0000000�00000030213�14607666711�0027763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import * as Components from '../components/index.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Battery'), description: _('Exchange battery information'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Battery', incomingCapabilities: [ 'kdeconnect.battery', 'kdeconnect.battery.request', ], outgoingCapabilities: [ 'kdeconnect.battery', 'kdeconnect.battery.request', ], actions: {}, }; /** * Battery Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/battery */ const BatteryPlugin = GObject.registerClass({ GTypeName: 'GSConnectBatteryPlugin', }, class BatteryPlugin extends Plugin { _init(device) { super._init(device, 'battery'); // Setup Cache; defaults are 90 minute charge, 1 day discharge this._chargeState = [54, 0, -1]; this._dischargeState = [864, 0, -1]; this._thresholdLevel = 25; this.cacheProperties([ '_chargeState', '_dischargeState', '_thresholdLevel', ]); // Export battery state as GAction this.__state = new Gio.SimpleAction({ name: 'battery', parameter_type: new GLib.VariantType('(bsii)'), state: this.state, }); this.device.add_action(this.__state); // Local Battery (UPower) this._upower = null; this._sendStatisticsId = this.settings.connect( 'changed::send-statistics', this._onSendStatisticsChanged.bind(this) ); this._onSendStatisticsChanged(this.settings); } get charging() { if (this._charging === undefined) this._charging = false; return this._charging; } get icon_name() { let icon; if (this.level === -1) return 'battery-missing-symbolic'; else if (this.level === 100) return 'battery-full-charged-symbolic'; else if (this.level < 3) icon = 'battery-empty'; else if (this.level < 10) icon = 'battery-caution'; else if (this.level < 30) icon = 'battery-low'; else if (this.level < 60) icon = 'battery-good'; else if (this.level >= 60) icon = 'battery-full'; if (this.charging) return `${icon}-charging-symbolic`; return `${icon}-symbolic`; } get level() { // This is what KDE Connect returns if the remote battery plugin is // disabled or still being loaded if (this._level === undefined) this._level = -1; return this._level; } get time() { if (this._time === undefined) this._time = 0; return this._time; } get state() { return new GLib.Variant( '(bsii)', [this.charging, this.icon_name, this.level, this.time] ); } cacheLoaded() { this._initEstimate(); this._sendState(); } clearCache() { this._chargeState = [54, 0, -1]; this._dischargeState = [864, 0, -1]; this._thresholdLevel = 25; this._initEstimate(); } connected() { super.connected(); this._requestState(); this._sendState(); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.battery': this._receiveState(packet); break; case 'kdeconnect.battery.request': this._sendState(); break; } } _onSendStatisticsChanged() { if (this.settings.get_boolean('send-statistics')) this._monitorState(); else this._unmonitorState(); } /** * Recalculate and update the estimated time remaining, but not the rate. */ _initEstimate() { let rate, level; // elision of [rate, time, level] if (this.charging) [rate,, level] = this._chargeState; else [rate,, level] = this._dischargeState; if (!Number.isFinite(rate) || rate < 1) rate = this.charging ? 864 : 90; if (!Number.isFinite(level) || level < 0) level = this.level; // Update the time remaining if (rate && this.charging) this._time = Math.floor(rate * (100 - level)); else if (rate && !this.charging) this._time = Math.floor(rate * level); this.__state.state = this.state; } /** * Recalculate the (dis)charge rate and update the estimated time remaining. */ _updateEstimate() { let rate, time, level; const newTime = Math.floor(Date.now() / 1000); const newLevel = this.level; // Load the state; ensure we have sane values for calculation if (this.charging) [rate, time, level] = this._chargeState; else [rate, time, level] = this._dischargeState; if (!Number.isFinite(rate) || rate < 1) rate = this.charging ? 54 : 864; if (!Number.isFinite(time) || time <= 0) time = newTime; if (!Number.isFinite(level) || level < 0) level = newLevel; // Update the rate; use a weighted average to account for missed changes // NOTE: (rate = seconds/percent) const ldiff = this.charging ? newLevel - level : level - newLevel; const tdiff = newTime - time; const newRate = tdiff / ldiff; if (newRate && Number.isFinite(newRate)) rate = Math.floor((rate * 0.4) + (newRate * 0.6)); // Store the state for the next recalculation if (this.charging) this._chargeState = [rate, newTime, newLevel]; else this._dischargeState = [rate, newTime, newLevel]; // Update the time remaining if (rate && this.charging) this._time = Math.floor(rate * (100 - newLevel)); else if (rate && !this.charging) this._time = Math.floor(rate * newLevel); this.__state.state = this.state; } /** * Notify the user the remote battery is full. */ _fullBatteryNotification() { if (!this.settings.get_boolean('full-battery-notification')) return; // Offer the option to ring the device, if available let buttons = []; if (this.device.get_action_enabled('ring')) { buttons = [{ label: _('Ring'), action: 'ring', parameter: null, }]; } this.device.showNotification({ id: 'battery|full', // TRANSLATORS: eg. Google Pixel: Battery is full title: _('%s: Battery is full').format(this.device.name), // TRANSLATORS: when the battery is fully charged body: _('Fully Charged'), icon: Gio.ThemedIcon.new('battery-full-charged-symbolic'), buttons: buttons, }); } /** * Notify the user the remote battery is at custom charge level. */ _customBatteryNotification() { if (!this.settings.get_boolean('custom-battery-notification')) return; // Offer the option to ring the device, if available let buttons = []; if (this.device.get_action_enabled('ring')) { buttons = [{ label: _('Ring'), action: 'ring', parameter: null, }]; } this.device.showNotification({ id: 'battery|custom', // TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level title: _('%s: Battery has reached custom charge level').format(this.device.name), // TRANSLATORS: when the battery has reached custom charge level body: _('%d%% Charged').format(this.level), icon: Gio.ThemedIcon.new('battery-full-charged-symbolic'), buttons: buttons, }); } /** * Notify the user the remote battery is low. */ _lowBatteryNotification() { if (!this.settings.get_boolean('low-battery-notification')) return; // Offer the option to ring the device, if available let buttons = []; if (this.device.get_action_enabled('ring')) { buttons = [{ label: _('Ring'), action: 'ring', parameter: null, }]; } this.device.showNotification({ id: 'battery|low', // TRANSLATORS: eg. Google Pixel: Battery is low title: _('%s: Battery is low').format(this.device.name), // TRANSLATORS: eg. 15% remaining body: _('%d%% remaining').format(this.level), icon: Gio.ThemedIcon.new('battery-caution-symbolic'), buttons: buttons, }); } /** * Handle a remote battery update. * * @param {Core.Packet} packet - A kdeconnect.battery packet */ _receiveState(packet) { // Charging state changed this._charging = packet.body.isCharging; // Level changed if (this._level !== packet.body.currentCharge) { this._level = packet.body.currentCharge; // If the level is above the threshold hide the notification if (this._level > this._thresholdLevel) this.device.hideNotification('battery|low'); // The level just changed to/from custom level while charging if ((this._level === this.settings.get_uint('custom-battery-notification-value')) && this._charging) this._customBatteryNotification(); else this.device.hideNotification('battery|custom'); // The level just changed to/from full if (this._level === 100) this._fullBatteryNotification(); else this.device.hideNotification('battery|full'); } // Device considers the level low if (packet.body.thresholdEvent > 0) { this._lowBatteryNotification(); this._thresholdLevel = this.level; } this._updateEstimate(); } /** * Request the remote battery's current state */ _requestState() { this.device.sendPacket({ type: 'kdeconnect.battery.request', body: {request: true}, }); } /** * Report the local battery's current state */ _sendState() { if (this._upower === null || !this._upower.is_present) return; this.device.sendPacket({ type: 'kdeconnect.battery', body: { currentCharge: this._upower.level, isCharging: this._upower.charging, thresholdEvent: this._upower.threshold, }, }); } /* * UPower monitoring methods */ _monitorState() { try { // Currently only true if the remote device is a desktop (rare) const incoming = this.device.settings.get_strv('incoming-capabilities'); if (!incoming.includes('kdeconnect.battery')) return; this._upower = Components.acquire('upower'); this._upowerId = this._upower.connect( 'changed', this._sendState.bind(this) ); this._sendState(); } catch (e) { logError(e, this.device.name); this._unmonitorState(); } } _unmonitorState() { try { if (this._upower === null) return; this._upower.disconnect(this._upowerId); this._upower = Components.release('upower'); } catch (e) { logError(e, this.device.name); } } destroy() { this.device.remove_action('battery'); this.settings.disconnect(this._sendStatisticsId); this._unmonitorState(); super.destroy(); } }); export default BatteryPlugin; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/clipboard.js������������������0000664�0000000�0000000�00000012063�14607666711�0030253�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GObject from 'gi://GObject'; import * as Components from '../components/index.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Clipboard'), description: _('Share the clipboard content'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Clipboard', incomingCapabilities: [ 'kdeconnect.clipboard', 'kdeconnect.clipboard.connect', ], outgoingCapabilities: [ 'kdeconnect.clipboard', 'kdeconnect.clipboard.connect', ], actions: { clipboardPush: { label: _('Clipboard Push'), icon_name: 'edit-paste-symbolic', parameter_type: null, incoming: [], outgoing: ['kdeconnect.clipboard'], }, clipboardPull: { label: _('Clipboard Pull'), icon_name: 'edit-copy-symbolic', parameter_type: null, incoming: ['kdeconnect.clipboard'], outgoing: [], }, }, }; /** * Clipboard Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/clipboard */ const ClipboardPlugin = GObject.registerClass({ GTypeName: 'GSConnectClipboardPlugin', }, class ClipboardPlugin extends Plugin { _init(device) { super._init(device, 'clipboard'); this._clipboard = Components.acquire('clipboard'); // Watch local clipboard for changes this._textChangedId = this._clipboard.connect( 'notify::text', this._onLocalClipboardChanged.bind(this) ); // Buffer content to allow selective sync this._localBuffer = this._clipboard.text; this._localTimestamp = 0; this._remoteBuffer = null; } connected() { super.connected(); // TODO: if we're not auto-syncing local->remote, but we are doing the // reverse, it's possible older remote content will end up // overwriting newer local content. if (!this.settings.get_boolean('send-content')) return; if (this._localBuffer === null && this._localTimestamp === 0) return; this.device.sendPacket({ type: 'kdeconnect.clipboard.connect', body: { content: this._localBuffer, timestamp: this._localTimestamp, }, }); } handlePacket(packet) { if (!packet.body.hasOwnProperty('content')) return; switch (packet.type) { case 'kdeconnect.clipboard': this._handleContent(packet); break; case 'kdeconnect.clipboard.connect': this._handleConnectContent(packet); break; } } _handleContent(packet) { this._onRemoteClipboardChanged(packet.body.content); } _handleConnectContent(packet) { if (packet.body.hasOwnProperty('timestamp') && packet.body.timestamp > this._localTimestamp) this._onRemoteClipboardChanged(packet.body.content); } /* * Store the local clipboard content and forward it if enabled */ _onLocalClipboardChanged(clipboard, pspec) { this._localBuffer = clipboard.text; this._localTimestamp = Date.now(); if (this.settings.get_boolean('send-content')) this.clipboardPush(); } /* * Store the remote clipboard content and apply it if enabled */ _onRemoteClipboardChanged(text) { this._remoteBuffer = text; if (this.settings.get_boolean('receive-content')) this.clipboardPull(); } /** * Copy to the remote clipboard; called by _onLocalClipboardChanged() */ clipboardPush() { // Don't sync if the clipboard is empty or not text if (this._localTimestamp === 0) return; if (this._remoteBuffer !== this._localBuffer) { this._remoteBuffer = this._localBuffer; // If the buffer is %null, the clipboard contains non-text content, // so we neither clear the remote clipboard nor pass the content if (this._localBuffer !== null) { this.device.sendPacket({ type: 'kdeconnect.clipboard', body: { content: this._localBuffer, }, }); } } } /** * Copy from the remote clipboard; called by _onRemoteClipboardChanged() */ clipboardPull() { if (this._localBuffer !== this._remoteBuffer) { this._localBuffer = this._remoteBuffer; this._localTimestamp = Date.now(); this._clipboard.text = this._remoteBuffer; } } destroy() { if (this._clipboard && this._textChangedId) { this._clipboard.disconnect(this._textChangedId); this._clipboard = Components.release('clipboard'); } super.destroy(); } }); export default ClipboardPlugin; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/connectivity_report.js��������0000664�0000000�0000000�00000011724�14607666711�0032430�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Connectivity Report'), description: _('Display connectivity status'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.ConnectivityReport', incomingCapabilities: [ 'kdeconnect.connectivity_report', ], outgoingCapabilities: [ 'kdeconnect.connectivity_report.request', ], actions: {}, }; /** * Connectivity Report Plugin * https://invent.kde.org/network/kdeconnect-kde/-/tree/master/plugins/connectivity_report */ const ConnectivityReportPlugin = GObject.registerClass({ GTypeName: 'GSConnectConnectivityReportPlugin', }, class ConnectivityReportPlugin extends Plugin { _init(device) { super._init(device, 'connectivity_report'); // Export connectivity state as GAction this.__state = new Gio.SimpleAction({ name: 'connectivityReport', // ( // cellular_network_type, // cellular_network_type_icon, // cellular_network_strength(0..4), // cellular_network_strength_icon, // ) parameter_type: new GLib.VariantType('(ssis)'), state: this.state, }); this.device.add_action(this.__state); } get signal_strength() { if (this._signalStrength === undefined) this._signalStrength = -1; return this._signalStrength; } get network_type() { if (this._networkType === undefined) this._networkType = ''; return this._networkType; } get signal_strength_icon_name() { if (this.signal_strength === 0) return 'network-cellular-signal-none-symbolic'; // SIGNAL_STRENGTH_NONE_OR_UNKNOWN else if (this.signal_strength === 1) return 'network-cellular-signal-weak-symbolic'; // SIGNAL_STRENGTH_POOR else if (this.signal_strength === 2) return 'network-cellular-signal-ok-symbolic'; // SIGNAL_STRENGTH_MODERATE else if (this.signal_strength === 3) return 'network-cellular-signal-good-symbolic'; // SIGNAL_STRENGTH_GOOD else if (this.signal_strength >= 4) return 'network-cellular-signal-excellent-symbolic'; // SIGNAL_STRENGTH_GREAT return 'network-cellular-offline-symbolic'; // OFF (signal_strength == -1) } get network_type_icon_name() { if (this.network_type === 'GSM' || this.network_type === 'CDMA' || this.network_type === 'iDEN') return 'network-cellular-2g-symbolic'; else if (this.network_type === 'UMTS' || this.network_type === 'CDMA2000') return 'network-cellular-3g-symbolic'; else if (this.network_type === 'LTE') return 'network-cellular-4g-symbolic'; else if (this.network_type === 'EDGE') return 'network-cellular-edge-symbolic'; else if (this.network_type === 'GPRS') return 'network-cellular-gprs-symbolic'; else if (this.network_type === 'HSPA') return 'network-cellular-hspa-symbolic'; else if (this.network_type === '5G') return 'network-cellular-5g-symbolic'; return 'network-cellular-symbolic'; } get state() { return new GLib.Variant( '(ssis)', [ this.network_type, this.network_type_icon_name, this.signal_strength, this.signal_strength_icon_name, ] ); } connected() { super.connected(); this._requestState(); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.connectivity_report': this._receiveState(packet); break; } } /** * Handle a remote state update. * * @param {Core.Packet} packet - A kdeconnect.connectivity_report packet */ _receiveState(packet) { if (packet.body.signalStrengths) { // TODO: Only first SIM (subscriptionID) is supported at the moment const subs = Object.keys(packet.body.signalStrengths); const firstSub = Math.min.apply(null, subs); const data = packet.body.signalStrengths[firstSub]; this._networkType = data.networkType; this._signalStrength = data.signalStrength; } // Update DBus state this.__state.state = this.state; } /** * Request the remote device's connectivity state */ _requestState() { this.device.sendPacket({ type: 'kdeconnect.connectivity_report.request', body: {}, }); } destroy() { this.device.remove_action('connectivity_report'); super.destroy(); } }); export default ConnectivityReportPlugin; ��������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/contacts.js�������������������0000664�0000000�0000000�00000033263�14607666711�0030137�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Plugin from '../plugin.js'; import Contacts from '../components/contacts.js'; /* * We prefer libebook's vCard parser if it's available */ let EBookContacts; export const setEBookContacts = (ebook) => { // This function is only for tests to call! EBookContacts = ebook; }; try { EBookContacts = (await import('gi://EBookContacts')).default; } catch (e) { EBookContacts = null; } export const Metadata = { label: _('Contacts'), description: _('Access contacts of the paired device'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Contacts', incomingCapabilities: [ 'kdeconnect.contacts.response_uids_timestamps', 'kdeconnect.contacts.response_vcards', ], outgoingCapabilities: [ 'kdeconnect.contacts.request_all_uids_timestamps', 'kdeconnect.contacts.request_vcards_by_uid', ], actions: {}, }; /* * vCard 2.1 Patterns */ const VCARD_FOLDING = /\r\n |\r |\n |=\n/g; const VCARD_SUPPORTED = /^fn|tel|photo|x-kdeconnect/i; const VCARD_BASIC = /^([^:;]+):(.+)$/; const VCARD_TYPED = /^([^:;]+);([^:]+):(.+)$/; const VCARD_TYPED_KEY = /item\d{1,2}\./; const VCARD_TYPED_META = /([a-z]+)=(.*)/i; /** * Contacts Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/contacts */ const ContactsPlugin = GObject.registerClass({ GTypeName: 'GSConnectContactsPlugin', }, class ContactsPlugin extends Plugin { _init(device) { super._init(device, 'contacts'); this._store = new Contacts(device.id); this._store.fetch = this._requestUids.bind(this); // Notify when the store is ready this._contactsStoreReadyId = this._store.connect( 'notify::context', () => this.device.notify('contacts') ); // Notify if the contacts source changes this._contactsSourceChangedId = this.settings.connect( 'changed::contacts-source', () => this.device.notify('contacts') ); // Load the cache this._store.load(); } clearCache() { this._store.clear(); } connected() { super.connected(); this._requestUids(); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.contacts.response_uids_timestamps': this._handleUids(packet); break; case 'kdeconnect.contacts.response_vcards': this._handleVCards(packet); break; } } _handleUids(packet) { try { const contacts = this._store.contacts; const remote_uids = packet.body.uids; let removed = false; delete packet.body.uids; // Usually a failed request, so avoid wiping the cache if (remote_uids.length === 0) return; // Delete any contacts that were removed on the device for (let i = 0, len = contacts.length; i < len; i++) { const contact = contacts[i]; if (!remote_uids.includes(contact.id)) { this._store.remove(contact.id, false); removed = true; } } // Build a list of new or updated contacts const uids = []; for (const [uid, timestamp] of Object.entries(packet.body)) { const contact = this._store.get_contact(uid); if (!contact || contact.timestamp !== timestamp) uids.push(uid); } // Send a request for any new or updated contacts if (uids.length) this._requestVCards(uids); // If we removed any contacts, save the cache if (removed) this._store.save(); } catch (e) { logError(e); } } /** * Decode a string encoded as "QUOTED-PRINTABLE" and return a regular string * * See: https://github.com/mathiasbynens/quoted-printable/blob/master/src/quoted-printable.js * * @param {string} input - The QUOTED-PRINTABLE string * @return {string} The decoded string */ _decodeQuotedPrintable(input) { return input // https://tools.ietf.org/html/rfc2045#section-6.7, rule 3 .replace(/[\t\x20]$/gm, '') // Remove hard line breaks preceded by `=` .replace(/=(?:\r\n?|\n|$)/g, '') // https://tools.ietf.org/html/rfc2045#section-6.7, note 1. .replace(/=([a-fA-F0-9]{2})/g, ($0, $1) => { const codePoint = parseInt($1, 16); return String.fromCharCode(codePoint); }); } /** * Decode a string encoded as "UTF-8" and return a regular string * * See: https://github.com/kvz/locutus/blob/master/src/php/xml/utf8_decode.js * * @param {string} input - The UTF-8 string * @return {string} The decoded string */ _decodeUTF8(input) { try { const output = []; let i = 0; let c1 = 0; let seqlen = 0; while (i < input.length) { c1 = input.charCodeAt(i) & 0xFF; seqlen = 0; if (c1 <= 0xBF) { c1 &= 0x7F; seqlen = 1; } else if (c1 <= 0xDF) { c1 &= 0x1F; seqlen = 2; } else if (c1 <= 0xEF) { c1 &= 0x0F; seqlen = 3; } else { c1 &= 0x07; seqlen = 4; } for (let ai = 1; ai < seqlen; ++ai) c1 = ((c1 << 0x06) | (input.charCodeAt(ai + i) & 0x3F)); if (seqlen === 4) { c1 -= 0x10000; output.push(String.fromCharCode(0xD800 | ((c1 >> 10) & 0x3FF))); output.push(String.fromCharCode(0xDC00 | (c1 & 0x3FF))); } else { output.push(String.fromCharCode(c1)); } i += seqlen; } return output.join(''); // Fallback to old unfaithful } catch (e) { try { return decodeURIComponent(escape(input)); // Say "chowdah" frenchie! } catch (e) { debug(e, `Failed to decode UTF-8 VCard field ${input}`); return input; } } } /** * Parse a vCard (v2.1 only) and return a dictionary of the fields * * See: http://jsfiddle.net/ARTsinn/P2t2P/ * * @param {string} vcard_data - The raw VCard data * @return {Object} dictionary of vCard data */ _parseVCard21(vcard_data) { // vcard skeleton const vcard = { fn: _('Unknown Contact'), tel: [], }; // Remove line folding and split const unfolded = vcard_data.replace(VCARD_FOLDING, ''); const lines = unfolded.split(/\r\n|\r|\n/); for (let i = 0, len = lines.length; i < len; i++) { const line = lines[i]; let results, key, type, value; // Empty line or a property we aren't interested in if (!line || !line.match(VCARD_SUPPORTED)) continue; // Basic Fields (fn, x-kdeconnect-timestamp, etc) if ((results = line.match(VCARD_BASIC))) { [, key, value] = results; vcard[key.toLowerCase()] = value; continue; } // Typed Fields (tel, adr, etc) if ((results = line.match(VCARD_TYPED))) { [, key, type, value] = results; key = key.replace(VCARD_TYPED_KEY, '').toLowerCase(); value = value.split(';'); type = type.split(';'); // Type(s) const meta = {}; for (let i = 0, len = type.length; i < len; i++) { const res = type[i].match(VCARD_TYPED_META); if (res) meta[res[1]] = res[2]; else meta[`type${i === 0 ? '' : i}`] = type[i].toLowerCase(); } // Value(s) if (vcard[key] === undefined) vcard[key] = []; // Decode QUOTABLE-PRINTABLE if (meta.ENCODING && meta.ENCODING === 'QUOTED-PRINTABLE') { delete meta.ENCODING; value = value.map(v => this._decodeQuotedPrintable(v)); } // Decode UTF-8 if (meta.CHARSET && meta.CHARSET === 'UTF-8') { delete meta.CHARSET; value = value.map(v => this._decodeUTF8(v)); } // Special case for FN (full name) if (key === 'fn') vcard[key] = value[0]; else vcard[key].push({meta: meta, value: value}); } } return vcard; } /** * Parse a vCard (v2.1 only) using native JavaScript and add it to the * contact store. * * @param {string} uid - The contact UID * @param {string} vcard_data - The raw vCard data */ async _parseVCardNative(uid, vcard_data) { try { const vcard = this._parseVCard21(vcard_data); const contact = { id: uid, name: vcard.fn, numbers: [], origin: 'device', timestamp: parseInt(vcard['x-kdeconnect-timestamp']), }; // Phone Numbers contact.numbers = vcard.tel.map(entry => { let type = 'unknown'; if (entry.meta && entry.meta.type) type = entry.meta.type; return {type: type, value: entry.value[0]}; }); // Avatar if (vcard.photo) { const data = GLib.base64_decode(vcard.photo[0].value[0]); contact.avatar = await this._store.storeAvatar(data); } this._store.add(contact); } catch (e) { debug(e, `Failed to parse VCard contact ${uid}`); } } /** * Parse a vCard using libebook and add it to the contact store. * * @param {string} uid - The contact UID * @param {string} vcard_data - The raw vCard data */ async _parseVCard(uid, vcard_data) { try { const contact = { id: uid, name: _('Unknown Contact'), numbers: [], origin: 'device', timestamp: 0, }; const evcard = EBookContacts.VCard.new_from_string(vcard_data); const attrs = evcard.get_attributes(); for (let i = 0, len = attrs.length; i < len; i++) { const attr = attrs[i]; let data, number; switch (attr.get_name().toLowerCase()) { case 'fn': contact.name = attr.get_value(); break; case 'tel': number = {value: attr.get_value(), type: 'unknown'}; if (attr.has_type('CELL')) number.type = 'cell'; else if (attr.has_type('HOME')) number.type = 'home'; else if (attr.has_type('WORK')) number.type = 'work'; contact.numbers.push(number); break; case 'x-kdeconnect-timestamp': contact.timestamp = parseInt(attr.get_value()); break; case 'photo': data = GLib.base64_decode(attr.get_value()); contact.avatar = await this._store.storeAvatar(data); break; } } this._store.add(contact); } catch (e) { debug(e, `Failed to parse VCard contact ${uid}`); } } /** * Handle an incoming list of contact vCards and pass them to the best * available parser. * * @param {Core.Packet} packet - A `kdeconnect.contacts.response_vcards` */ _handleVCards(packet) { try { // We don't use this delete packet.body.uids; // Parse each vCard and add the contact for (const [uid, vcard] of Object.entries(packet.body)) { if (EBookContacts) this._parseVCard(uid, vcard); else this._parseVCardNative(uid, vcard); } } catch (e) { logError(e, this.device.name); } } /** * Request a list of contact UIDs with timestamps. */ _requestUids() { this.device.sendPacket({ type: 'kdeconnect.contacts.request_all_uids_timestamps', }); } /** * Request the vCards for @uids. * * @param {string[]} uids - A list of contact UIDs */ _requestVCards(uids) { this.device.sendPacket({ type: 'kdeconnect.contacts.request_vcards_by_uid', body: { uids: uids, }, }); } destroy() { this._store.disconnect(this._contactsStoreReadyId); this.settings.disconnect(this._contactsSourceChangedId); super.destroy(); } }); export default ContactsPlugin; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/findmyphone.js����������������0000664�0000000�0000000�00000014513�14607666711�0030636�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import * as Components from '../components/index.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Find My Phone'), description: _('Ring your paired device'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.FindMyPhone', incomingCapabilities: ['kdeconnect.findmyphone.request'], outgoingCapabilities: ['kdeconnect.findmyphone.request'], actions: { ring: { label: _('Ring'), icon_name: 'phonelink-ring-symbolic', parameter_type: null, incoming: [], outgoing: ['kdeconnect.findmyphone.request'], }, }, }; /** * FindMyPhone Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/findmyphone */ const FindMyPhonePlugin = GObject.registerClass({ GTypeName: 'GSConnectFindMyPhonePlugin', }, class FindMyPhonePlugin extends Plugin { _init(device) { super._init(device, 'findmyphone'); this._dialog = null; this._player = Components.acquire('sound'); this._mixer = Components.acquire('pulseaudio'); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.findmyphone.request': this._handleRequest(); break; } } /** * Handle an incoming location request. */ _handleRequest() { try { // If this is a second request, stop announcing and return if (this._dialog !== null) { this._dialog.response(Gtk.ResponseType.DELETE_EVENT); return; } this._dialog = new Dialog({ device: this.device, plugin: this, }); this._dialog.connect('response', () => { this._dialog = null; }); } catch (e) { this._cancelRequest(); logError(e, this.device.name); } } /** * Cancel any ongoing ringing and destroy the dialog. */ _cancelRequest() { if (this._dialog !== null) this._dialog.response(Gtk.ResponseType.DELETE_EVENT); } /** * Request that the remote device announce it's location */ ring() { this.device.sendPacket({ type: 'kdeconnect.findmyphone.request', body: {}, }); } destroy() { this._cancelRequest(); if (this._mixer !== undefined) this._mixer = Components.release('pulseaudio'); if (this._player !== undefined) this._player = Components.release('sound'); super.destroy(); } }); /* * Used to ensure 'audible-bell' is enabled for fallback */ const _WM_SETTINGS = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences', path: '/org/gnome/desktop/wm/preferences/', }); /** * A custom GtkMessageDialog for alerting of incoming requests */ const Dialog = GObject.registerClass({ GTypeName: 'GSConnectFindMyPhoneDialog', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE, GObject.Object ), 'plugin': GObject.ParamSpec.object( 'plugin', 'Plugin', 'The plugin providing messages', GObject.ParamFlags.READWRITE, GObject.Object ), }, }, class Dialog extends Gtk.MessageDialog { _init(params) { super._init({ buttons: Gtk.ButtonsType.CLOSE, device: params.device, image: new Gtk.Image({ icon_name: 'phonelink-ring-symbolic', pixel_size: 512, halign: Gtk.Align.CENTER, hexpand: true, valign: Gtk.Align.CENTER, vexpand: true, visible: true, }), plugin: params.plugin, urgency_hint: true, }); this.set_keep_above(true); this.maximize(); this.message_area.destroy(); // If an output stream is available start fading the volume up if (this.plugin._mixer && this.plugin._mixer.output) { this._stream = this.plugin._mixer.output; this._previousMuted = this._stream.muted; this._previousVolume = this._stream.volume; this._stream.muted = false; this._stream.fade(0.85, 15); // Otherwise ensure audible-bell is enabled } else { this._previousBell = _WM_SETTINGS.get_boolean('audible-bell'); _WM_SETTINGS.set_boolean('audible-bell', true); } // Start the alarm if (this.plugin._player !== undefined) this.plugin._player.loopSound('phone-incoming-call', this.cancellable); // Show the dialog this.show_all(); } vfunc_key_press_event(event) { this.response(Gtk.ResponseType.DELETE_EVENT); return Gdk.EVENT_STOP; } vfunc_motion_notify_event(event) { this.response(Gtk.ResponseType.DELETE_EVENT); return Gdk.EVENT_STOP; } vfunc_response(response_id) { // Stop the alarm this.cancellable.cancel(); // Restore the mixer level if (this._stream) { this._stream.muted = this._previousMuted; this._stream.fade(this._previousVolume); // Restore the audible-bell } else { _WM_SETTINGS.set_boolean('audible-bell', this._previousBell); } this.destroy(); } get cancellable() { if (this._cancellable === undefined) this._cancellable = new Gio.Cancellable(); return this._cancellable; } get device() { if (this._device === undefined) this._device = null; return this._device; } set device(device) { this._device = device; } get plugin() { if (this._plugin === undefined) this._plugin = null; return this._plugin; } set plugin(plugin) { this._plugin = plugin; } }); export default FindMyPhonePlugin; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/index.js����������������������0000664�0000000�0000000�00000002070�14607666711�0027420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as battery from './battery.js'; import * as clipboard from './clipboard.js'; import * as connectivity_report from './connectivity_report.js'; import * as contacts from './contacts.js'; import * as findmyphone from './findmyphone.js'; import * as mousepad from './mousepad.js'; import * as mpris from './mpris.js'; import * as notification from './notification.js'; import * as ping from './ping.js'; import * as presenter from './presenter.js'; import * as runcommand from './runcommand.js'; import * as sftp from './sftp.js'; import * as share from './share.js'; import * as sms from './sms.js'; import * as systemvolume from './systemvolume.js'; import * as telephony from './telephony.js'; export default { battery, clipboard, connectivity_report, contacts, findmyphone, mousepad, mpris, notification, ping, presenter, runcommand, sftp, share, sms, systemvolume, telephony, }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/mousepad.js�������������������0000664�0000000�0000000�00000024002�14607666711�0030125�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import GObject from 'gi://GObject'; import * as Components from '../components/index.js'; import {InputDialog} from '../ui/mousepad.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Mousepad'), description: _('Enables the paired device to act as a remote mouse and keyboard'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Mousepad', incomingCapabilities: [ 'kdeconnect.mousepad.echo', 'kdeconnect.mousepad.request', 'kdeconnect.mousepad.keyboardstate', ], outgoingCapabilities: [ 'kdeconnect.mousepad.echo', 'kdeconnect.mousepad.request', 'kdeconnect.mousepad.keyboardstate', ], actions: { keyboard: { label: _('Remote Input'), icon_name: 'input-keyboard-symbolic', parameter_type: null, incoming: [ 'kdeconnect.mousepad.echo', 'kdeconnect.mousepad.keyboardstate', ], outgoing: ['kdeconnect.mousepad.request'], }, }, }; /** * A map of "KDE Connect" keyvals to Gdk */ const KeyMap = new Map([ [1, Gdk.KEY_BackSpace], [2, Gdk.KEY_Tab], [3, Gdk.KEY_Linefeed], [4, Gdk.KEY_Left], [5, Gdk.KEY_Up], [6, Gdk.KEY_Right], [7, Gdk.KEY_Down], [8, Gdk.KEY_Page_Up], [9, Gdk.KEY_Page_Down], [10, Gdk.KEY_Home], [11, Gdk.KEY_End], [12, Gdk.KEY_Return], [13, Gdk.KEY_Delete], [14, Gdk.KEY_Escape], [15, Gdk.KEY_Sys_Req], [16, Gdk.KEY_Scroll_Lock], [17, 0], [18, 0], [19, 0], [20, 0], [21, Gdk.KEY_F1], [22, Gdk.KEY_F2], [23, Gdk.KEY_F3], [24, Gdk.KEY_F4], [25, Gdk.KEY_F5], [26, Gdk.KEY_F6], [27, Gdk.KEY_F7], [28, Gdk.KEY_F8], [29, Gdk.KEY_F9], [30, Gdk.KEY_F10], [31, Gdk.KEY_F11], [32, Gdk.KEY_F12], ]); const KeyMapCodes = new Map([ [1, 14], [2, 15], [3, 101], [4, 105], [5, 103], [6, 106], [7, 108], [8, 104], [9, 109], [10, 102], [11, 107], [12, 28], [13, 111], [14, 1], [15, 99], [16, 70], [17, 0], [18, 0], [19, 0], [20, 0], [21, 59], [22, 60], [23, 61], [24, 62], [25, 63], [26, 64], [27, 65], [28, 66], [29, 67], [30, 68], [31, 87], [32, 88], ]); /** * Mousepad Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/mousepad * * TODO: support outgoing mouse events? */ const MousepadPlugin = GObject.registerClass({ GTypeName: 'GSConnectMousepadPlugin', Properties: { 'state': GObject.ParamSpec.boolean( 'state', 'State', 'Remote keyboard state', GObject.ParamFlags.READABLE, false ), }, }, class MousepadPlugin extends Plugin { _init(device) { super._init(device, 'mousepad'); if (!globalThis.HAVE_GNOME) this._input = Components.acquire('ydotool'); else this._input = Components.acquire('input'); this._shareControlChangedId = this.settings.connect( 'changed::share-control', this._sendState.bind(this) ); } get state() { if (this._state === undefined) this._state = false; return this._state; } connected() { super.connected(); this._sendState(); } disconnected() { super.disconnected(); this._state = false; this.notify('state'); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.mousepad.request': this._handleInput(packet.body); break; case 'kdeconnect.mousepad.echo': this._handleEcho(packet.body); break; case 'kdeconnect.mousepad.keyboardstate': this._handleState(packet); break; } } /** * Handle a input event. * * @param {Object} input - The body of a `kdeconnect.mousepad.request` */ _handleInput(input) { if (!this.settings.get_boolean('share-control')) return; let keysym; let modifiers = 0; const modifiers_codes = []; // These are ordered, as much as possible, to create the shortest code // path for high-frequency, low-latency events (eg. mouse movement) switch (true) { case input.hasOwnProperty('scroll'): this._input.scrollPointer(input.dx, input.dy); break; case (input.hasOwnProperty('dx') && input.hasOwnProperty('dy')): this._input.movePointer(input.dx, input.dy); break; case (input.hasOwnProperty('key') || input.hasOwnProperty('specialKey')): // NOTE: \u0000 sometimes sent in advance of a specialKey packet if (input.key && input.key === '\u0000') return; // Modifiers if (input.alt) { modifiers |= Gdk.ModifierType.MOD1_MASK; modifiers_codes.push(56); } if (input.ctrl) { modifiers |= Gdk.ModifierType.CONTROL_MASK; modifiers_codes.push(29); } if (input.shift) { modifiers |= Gdk.ModifierType.SHIFT_MASK; modifiers_codes.push(42); } if (input.super) { modifiers |= Gdk.ModifierType.SUPER_MASK; modifiers_codes.push(125); } // Regular key (printable ASCII or Unicode) if (input.key) { if (!globalThis.HAVE_GNOME) this._input.pressKeys(input.key, modifiers_codes); else this._input.pressKeys(input.key, modifiers); this._sendEcho(input); // Special key (eg. non-printable ASCII) } else if (input.specialKey && KeyMap.has(input.specialKey)) { if (!globalThis.HAVE_GNOME) { keysym = KeyMapCodes.get(input.specialKey); this._input.pressKeys(keysym, modifiers_codes); } else { keysym = KeyMap.get(input.specialKey); this._input.pressKeys(keysym, modifiers); } this._sendEcho(input); } break; case input.hasOwnProperty('singleclick'): this._input.clickPointer(Gdk.BUTTON_PRIMARY); break; case input.hasOwnProperty('doubleclick'): this._input.doubleclickPointer(Gdk.BUTTON_PRIMARY); break; case input.hasOwnProperty('middleclick'): this._input.clickPointer(Gdk.BUTTON_MIDDLE); break; case input.hasOwnProperty('rightclick'): this._input.clickPointer(Gdk.BUTTON_SECONDARY); break; case input.hasOwnProperty('singlehold'): this._input.pressPointer(Gdk.BUTTON_PRIMARY); break; case input.hasOwnProperty('singlerelease'): this._input.releasePointer(Gdk.BUTTON_PRIMARY); break; default: logError(new Error('Unknown input')); } } /** * Handle an echo/ACK of a event we sent, displaying it the dialog entry. * * @param {Object} input - The body of a `kdeconnect.mousepad.echo` */ _handleEcho(input) { if (!this._dialog || !this._dialog.visible) return; // Skip modifiers if (input.alt || input.ctrl || input.super) return; if (input.key) { this._dialog._isAck = true; this._dialog.entry.buffer.text += input.key; this._dialog._isAck = false; } else if (KeyMap.get(input.specialKey) === Gdk.KEY_BackSpace) { this._dialog.entry.emit('backspace'); } } /** * Handle a state change from the remote keyboard. This is an indication * that the remote keyboard is ready to accept input. * * @param {Object} packet - A `kdeconnect.mousepad.keyboardstate` packet */ _handleState(packet) { this._state = !!packet.body.state; this.notify('state'); } /** * Send an echo/ACK of @input, if requested * * @param {Object} input - The body of a 'kdeconnect.mousepad.request' */ _sendEcho(input) { if (!input.sendAck) return; delete input.sendAck; input.isAck = true; this.device.sendPacket({ type: 'kdeconnect.mousepad.echo', body: input, }); } /** * Send the local keyboard state * * @param {boolean} state - Whether we're ready to accept input */ _sendState() { this.device.sendPacket({ type: 'kdeconnect.mousepad.keyboardstate', body: { state: this.settings.get_boolean('share-control'), }, }); } /** * Open the Keyboard Input dialog */ keyboard() { if (this._dialog === undefined) { this._dialog = new InputDialog({ device: this.device, plugin: this, }); } this._dialog.present(); } destroy() { if (this._input !== undefined) { if (!globalThis.HAVE_GNOME) this._input = Components.release('ydotool'); else this._input = Components.release('input'); } if (this._dialog !== undefined) this._dialog.destroy(); this.settings.disconnect(this._shareControlChangedId); super.destroy(); } }); export default MousepadPlugin; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/mpris.js����������������������0000664�0000000�0000000�00000063676�14607666711�0027466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import * as Components from '../components/index.js'; import Config from '../../config.js'; import * as DBus from '../utils/dbus.js'; import {Player} from '../components/mpris.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('MPRIS'), description: _('Bidirectional remote media playback control'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.MPRIS', incomingCapabilities: ['kdeconnect.mpris', 'kdeconnect.mpris.request'], outgoingCapabilities: ['kdeconnect.mpris', 'kdeconnect.mpris.request'], actions: {}, }; /** * MPRIS Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/mpriscontrol * * See also: * https://specifications.freedesktop.org/mpris-spec/latest/ * https://github.com/GNOME/gnome-shell/blob/master/js/ui/mpris.js */ const MPRISPlugin = GObject.registerClass({ GTypeName: 'GSConnectMPRISPlugin', }, class MPRISPlugin extends Plugin { _init(device) { super._init(device, 'mpris'); this._players = new Map(); this._transferring = new WeakSet(); this._updating = new WeakSet(); this._mpris = Components.acquire('mpris'); this._playerAddedId = this._mpris.connect( 'player-added', this._sendPlayerList.bind(this) ); this._playerRemovedId = this._mpris.connect( 'player-removed', this._sendPlayerList.bind(this) ); this._playerChangedId = this._mpris.connect( 'player-changed', this._onPlayerChanged.bind(this) ); this._playerSeekedId = this._mpris.connect( 'player-seeked', this._onPlayerSeeked.bind(this) ); } connected() { super.connected(); this._requestPlayerList(); this._sendPlayerList(); } disconnected() { super.disconnected(); for (const [identity, player] of this._players) { this._players.delete(identity); player.destroy(); } } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.mpris': this._handleUpdate(packet); break; case 'kdeconnect.mpris.request': this._handleRequest(packet); break; } } /** * Handle a remote player update. * * @param {Core.Packet} packet - A `kdeconnect.mpris` */ _handleUpdate(packet) { try { if (packet.body.hasOwnProperty('playerList')) this._handlePlayerList(packet.body.playerList); else if (packet.body.hasOwnProperty('player')) this._handlePlayerUpdate(packet); } catch (e) { debug(e, this.device.name); } } /** * Handle an updated list of remote players. * * @param {string[]} playerList - A list of remote player names */ _handlePlayerList(playerList) { // Destroy removed players before adding new ones for (const player of this._players.values()) { if (!playerList.includes(player.Identity)) { this._players.delete(player.Identity); player.destroy(); } } for (const identity of playerList) { if (!this._players.has(identity)) { const player = new PlayerRemote(this.device, identity); this._players.set(identity, player); } // Always request player updates; packets are cheap this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: identity, requestNowPlaying: true, requestVolume: true, }, }); } } /** * Handle an update for a remote player. * * @param {Object} packet - A `kdeconnect.mpris` packet */ _handlePlayerUpdate(packet) { const player = this._players.get(packet.body.player); if (player === undefined) return; if (packet.body.hasOwnProperty('transferringAlbumArt')) player.handleAlbumArt(packet); else player.update(packet.body); } /** * Request a list of remote players. */ _requestPlayerList() { this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { requestPlayerList: true, }, }); } /** * Handle a request for player information or action. * * @param {Core.Packet} packet - a `kdeconnect.mpris.request` * @return {undefined} no return value */ _handleRequest(packet) { // A request for the list of players if (packet.body.hasOwnProperty('requestPlayerList')) return this._sendPlayerList(); // A request for an unknown player; send the list of players if (!this._mpris.hasPlayer(packet.body.player)) return this._sendPlayerList(); // An album art request if (packet.body.hasOwnProperty('albumArtUrl')) return this._sendAlbumArt(packet); // A player command this._handleCommand(packet); } /** * Handle an incoming player command or information request * * @param {Core.Packet} packet - A `kdeconnect.mpris.request` */ async _handleCommand(packet) { if (!this.settings.get_boolean('share-players')) return; let player; try { player = this._mpris.getPlayer(packet.body.player); if (player === undefined || this._updating.has(player)) return; this._updating.add(player); // Player Actions if (packet.body.hasOwnProperty('action')) { switch (packet.body.action) { case 'PlayPause': case 'Play': case 'Pause': case 'Next': case 'Previous': case 'Stop': player[packet.body.action](); break; default: debug(`unknown action: ${packet.body.action}`); } } // Player Properties if (packet.body.hasOwnProperty('setLoopStatus')) player.LoopStatus = packet.body.setLoopStatus; if (packet.body.hasOwnProperty('setShuffle')) player.Shuffle = packet.body.setShuffle; if (packet.body.hasOwnProperty('setVolume')) player.Volume = packet.body.setVolume / 100; if (packet.body.hasOwnProperty('Seek')) await player.Seek(packet.body.Seek); if (packet.body.hasOwnProperty('SetPosition')) { // We want to avoid implementing this as a seek operation, // because some players seek a fixed amount for every // seek request, only respecting the sign of the parameter. // (Chrome, for example, will only seek ±5 seconds, regardless // what value is passed to Seek().) const position = packet.body.SetPosition; const metadata = player.Metadata; if (metadata.hasOwnProperty('mpris:trackid')) { const trackId = metadata['mpris:trackid']; await player.SetPosition(trackId, position * 1000); } else { await player.Seek(position * 1000 - player.Position); } } // Information Request let hasResponse = false; const response = { type: 'kdeconnect.mpris', body: { player: packet.body.player, }, }; if (packet.body.hasOwnProperty('requestNowPlaying')) { hasResponse = true; Object.assign(response.body, { pos: Math.floor(player.Position / 1000), isPlaying: (player.PlaybackStatus === 'Playing'), canPause: player.CanPause, canPlay: player.CanPlay, canGoNext: player.CanGoNext, canGoPrevious: player.CanGoPrevious, canSeek: player.CanSeek, loopStatus: player.LoopStatus, shuffle: player.Shuffle, // default values for members that will be filled conditionally albumArtUrl: '', length: 0, artist: '', title: '', album: '', nowPlaying: '', volume: 0, }); const metadata = player.Metadata; if (metadata.hasOwnProperty('mpris:artUrl')) { const file = Gio.File.new_for_uri(metadata['mpris:artUrl']); response.body.albumArtUrl = file.get_uri(); } if (metadata.hasOwnProperty('mpris:length')) { const trackLen = Math.floor(metadata['mpris:length'] / 1000); response.body.length = trackLen; } if (metadata.hasOwnProperty('xesam:artist')) { const artists = metadata['xesam:artist']; response.body.artist = artists.join(', '); } if (metadata.hasOwnProperty('xesam:title')) response.body.title = metadata['xesam:title']; if (metadata.hasOwnProperty('xesam:album')) response.body.album = metadata['xesam:album']; // Now Playing if (response.body.artist && response.body.title) { response.body.nowPlaying = [ response.body.artist, response.body.title, ].join(' - '); } else if (response.body.artist) { response.body.nowPlaying = response.body.artist; } else if (response.body.title) { response.body.nowPlaying = response.body.title; } else { response.body.nowPlaying = _('Unknown'); } } if (packet.body.hasOwnProperty('requestVolume')) { hasResponse = true; response.body.volume = Math.floor(player.Volume * 100); } if (hasResponse) this.device.sendPacket(response); } catch (e) { debug(e, this.device.name); } finally { this._updating.delete(player); } } _onPlayerChanged(mpris, player) { if (!this.settings.get_boolean('share-players')) return; this._handleCommand({ body: { player: player.Identity, requestNowPlaying: true, requestVolume: true, }, }); } _onPlayerSeeked(mpris, player, offset) { // TODO: although we can handle full seeked signals, kdeconnect-android // does not, and expects a position update instead this.device.sendPacket({ type: 'kdeconnect.mpris', body: { player: player.Identity, pos: Math.floor(player.Position / 1000), // Seek: Math.floor(offset / 1000), }, }); } async _sendAlbumArt(packet) { let player; try { // Reject concurrent requests for album art player = this._mpris.getPlayer(packet.body.player); if (player === undefined || this._transferring.has(player)) return; // Ensure the requested albumArtUrl matches the current mpris:artUrl const metadata = player.Metadata; if (!metadata.hasOwnProperty('mpris:artUrl')) return; const file = Gio.File.new_for_uri(metadata['mpris:artUrl']); const request = Gio.File.new_for_uri(packet.body.albumArtUrl); if (file.get_uri() !== request.get_uri()) throw RangeError(`invalid URI "${packet.body.albumArtUrl}"`); // Transfer the album art this._transferring.add(player); const transfer = this.device.createTransfer(); transfer.addFile({ type: 'kdeconnect.mpris', body: { transferringAlbumArt: true, player: packet.body.player, albumArtUrl: packet.body.albumArtUrl, }, }, file); await transfer.start(); } catch (e) { debug(e, this.device.name); } finally { this._transferring.delete(player); } } /** * Send the list of player identities and indicate whether we support * transferring album art */ _sendPlayerList() { let playerList = []; if (this.settings.get_boolean('share-players')) playerList = this._mpris.getIdentities(); this.device.sendPacket({ type: 'kdeconnect.mpris', body: { playerList: playerList, supportAlbumArtPayload: true, }, }); } destroy() { if (this._mpris !== undefined) { this._mpris.disconnect(this._playerAddedId); this._mpris.disconnect(this._playerRemovedId); this._mpris.disconnect(this._playerChangedId); this._mpris.disconnect(this._playerSeekedId); this._mpris = Components.release('mpris'); } for (const [identity, player] of this._players) { this._players.delete(identity); player.destroy(); } super.destroy(); } }); /* * A class for mirroring a remote Media Player on DBus */ const PlayerRemote = GObject.registerClass({ GTypeName: 'GSConnectMPRISPlayerRemote', }, class PlayerRemote extends Player { _init(device, identity) { super._init(); this._device = device; this._Identity = identity; this._isPlaying = false; this._artist = null; this._title = null; this._album = null; this._length = 0; this._artUrl = null; this._ownerId = 0; this._connection = null; this._applicationIface = null; this._playerIface = null; } _getFile(albumArtUrl) { const hash = GLib.compute_checksum_for_string(GLib.ChecksumType.MD5, albumArtUrl, -1); const path = GLib.build_filenamev([Config.CACHEDIR, hash]); return Gio.File.new_for_uri(`file://${path}`); } _requestAlbumArt(state) { if (this._artUrl === state.albumArtUrl) return; const file = this._getFile(state.albumArtUrl); if (file.query_exists(null)) { this._artUrl = file.get_uri(); this._Metadata = undefined; this.notify('Metadata'); } else { this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, albumArtUrl: state.albumArtUrl, }, }); } } _updateMetadata(state) { let metadataChanged = false; if (state.hasOwnProperty('artist')) { if (this._artist !== state.artist) { this._artist = state.artist; metadataChanged = true; } } else if (this._artist) { this._artist = null; metadataChanged = true; } if (state.hasOwnProperty('title')) { if (this._title !== state.title) { this._title = state.title; metadataChanged = true; } } else if (this._title) { this._title = null; metadataChanged = true; } if (state.hasOwnProperty('album')) { if (this._album !== state.album) { this._album = state.album; metadataChanged = true; } } else if (this._album) { this._album = null; metadataChanged = true; } if (state.hasOwnProperty('length')) { if (this._length !== state.length * 1000) { this._length = state.length * 1000; metadataChanged = true; } } else if (this._length) { this._length = 0; metadataChanged = true; } if (state.hasOwnProperty('albumArtUrl')) { this._requestAlbumArt(state); } else if (this._artUrl) { this._artUrl = null; metadataChanged = true; } if (metadataChanged) { this._Metadata = undefined; this.notify('Metadata'); } } async export() { try { if (this._connection === null) { this._connection = await DBus.newConnection(); const MPRISIface = Config.DBUS.lookup_interface('org.mpris.MediaPlayer2'); const MPRISPlayerIface = Config.DBUS.lookup_interface('org.mpris.MediaPlayer2.Player'); if (this._applicationIface === null) { this._applicationIface = new DBus.Interface({ g_instance: this, g_connection: this._connection, g_object_path: '/org/mpris/MediaPlayer2', g_interface_info: MPRISIface, }); } if (this._playerIface === null) { this._playerIface = new DBus.Interface({ g_instance: this, g_connection: this._connection, g_object_path: '/org/mpris/MediaPlayer2', g_interface_info: MPRISPlayerIface, }); } } if (this._ownerId !== 0) return; const name = [ this.device.name, this.Identity, ].join('').replace(/[\W]*/g, ''); this._ownerId = Gio.bus_own_name_on_connection( this._connection, `org.mpris.MediaPlayer2.GSConnect.${name}`, Gio.BusNameOwnerFlags.NONE, null, null ); } catch (e) { debug(e, this.Identity); } } unexport() { if (this._ownerId === 0) return; Gio.bus_unown_name(this._ownerId); this._ownerId = 0; } /** * Download album art for the current track of the remote player. * * @param {Core.Packet} packet - A `kdeconnect.mpris` packet */ async handleAlbumArt(packet) { let file; try { file = this._getFile(packet.body.albumArtUrl); // Transfer the album art const transfer = this.device.createTransfer(); transfer.addFile(packet, file); await transfer.start(); this._artUrl = file.get_uri(); this._Metadata = undefined; this.notify('Metadata'); } catch (e) { debug(e, this.device.name); if (file) file.delete_async(GLib.PRIORITY_DEFAULT, null, null); } } /** * Update the internal state of the media player. * * @param {Core.Packet} state - The body of a `kdeconnect.mpris` packet */ update(state) { this.freeze_notify(); // Metadata if (state.hasOwnProperty('nowPlaying') || state.hasOwnProperty('artist') || state.hasOwnProperty('title')) this._updateMetadata(state); // Playback Status if (state.hasOwnProperty('isPlaying')) { if (this._isPlaying !== state.isPlaying) { this._isPlaying = state.isPlaying; this.notify('PlaybackStatus'); } } if (state.hasOwnProperty('canPlay')) { if (this.CanPlay !== state.canPlay) { this._CanPlay = state.canPlay; this.notify('CanPlay'); } } if (state.hasOwnProperty('canPause')) { if (this.CanPause !== state.canPause) { this._CanPause = state.canPause; this.notify('CanPause'); } } if (state.hasOwnProperty('canGoNext')) { if (this.CanGoNext !== state.canGoNext) { this._CanGoNext = state.canGoNext; this.notify('CanGoNext'); } } if (state.hasOwnProperty('canGoPrevious')) { if (this.CanGoPrevious !== state.canGoPrevious) { this._CanGoPrevious = state.canGoPrevious; this.notify('CanGoPrevious'); } } if (state.hasOwnProperty('pos')) this._Position = state.pos * 1000; if (state.hasOwnProperty('volume')) { if (this.Volume !== state.volume / 100) { this._Volume = state.volume / 100; this.notify('Volume'); } } this.thaw_notify(); if (!this._isPlaying && !this.CanControl) this.unexport(); else this.export(); } /* * Native properties */ get device() { return this._device; } /* * The org.mpris.MediaPlayer2.Player Interface */ get CanControl() { return (this.CanPlay || this.CanPause); } get Metadata() { if (this._Metadata === undefined) { this._Metadata = {}; if (this._artist) { this._Metadata['xesam:artist'] = new GLib.Variant('as', [this._artist]); } if (this._title) { this._Metadata['xesam:title'] = new GLib.Variant('s', this._title); } if (this._album) { this._Metadata['xesam:album'] = new GLib.Variant('s', this._album); } if (this._artUrl) { this._Metadata['mpris:artUrl'] = new GLib.Variant('s', this._artUrl); } this._Metadata['mpris:length'] = new GLib.Variant('x', this._length); } return this._Metadata; } get PlaybackStatus() { if (this._isPlaying) return 'Playing'; return 'Stopped'; } get Volume() { if (this._Volume === undefined) this._Volume = 0.3; return this._Volume; } set Volume(level) { if (this._Volume === level) return; this._Volume = level; this.notify('Volume'); this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, setVolume: Math.floor(this._Volume * 100), }, }); } Next() { if (!this.CanGoNext) return; this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, action: 'Next', }, }); } Pause() { if (!this.CanPause) return; this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, action: 'Pause', }, }); } Play() { if (!this.CanPlay) return; this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, action: 'Play', }, }); } PlayPause() { if (!this.CanPlay && !this.CanPause) return; this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, action: 'PlayPause', }, }); } Previous() { if (!this.CanGoPrevious) return; this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, action: 'Previous', }, }); } Seek(offset) { if (!this.CanSeek) return; this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, Seek: offset, }, }); } SetPosition(trackId, position) { debug(`${this._Identity}: SetPosition(${trackId}, ${position})`); if (!this.CanControl || !this.CanSeek) return; this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, SetPosition: position / 1000, }, }); } Stop() { if (!this.CanControl) return; this.device.sendPacket({ type: 'kdeconnect.mpris.request', body: { player: this.Identity, action: 'Stop', }, }); } destroy() { this.unexport(); if (this._connection) { this._connection.close(null, null); this._connection = null; if (this._applicationIface) { this._applicationIface.destroy(); this._applicationIface = null; } if (this._playerIface) { this._playerIface.destroy(); this._playerIface = null; } } } }); export default MPRISPlugin; ������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/notification.js���������������0000664�0000000�0000000�00000054321�14607666711�0031005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import * as Components from '../components/index.js'; import Config from '../../config.js'; import Plugin from '../plugin.js'; import ReplyDialog from '../ui/notification.js'; export const Metadata = { label: _('Notifications'), description: _('Share notifications with the paired device'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Notification', incomingCapabilities: [ 'kdeconnect.notification', 'kdeconnect.notification.request', ], outgoingCapabilities: [ 'kdeconnect.notification', 'kdeconnect.notification.action', 'kdeconnect.notification.reply', 'kdeconnect.notification.request', ], actions: { withdrawNotification: { label: _('Cancel Notification'), icon_name: 'preferences-system-notifications-symbolic', parameter_type: new GLib.VariantType('s'), incoming: [], outgoing: ['kdeconnect.notification'], }, closeNotification: { label: _('Close Notification'), icon_name: 'preferences-system-notifications-symbolic', parameter_type: new GLib.VariantType('s'), incoming: [], outgoing: ['kdeconnect.notification.request'], }, replyNotification: { label: _('Reply Notification'), icon_name: 'preferences-system-notifications-symbolic', parameter_type: new GLib.VariantType('(ssa{ss})'), incoming: ['kdeconnect.notification'], outgoing: ['kdeconnect.notification.reply'], }, sendNotification: { label: _('Send Notification'), icon_name: 'preferences-system-notifications-symbolic', parameter_type: new GLib.VariantType('a{sv}'), incoming: [], outgoing: ['kdeconnect.notification'], }, activateNotification: { label: _('Activate Notification'), icon_name: 'preferences-system-notifications-symbolic', parameter_type: new GLib.VariantType('(ss)'), incoming: [], outgoing: ['kdeconnect.notification.action'], }, }, }; // A regex for our custom notificaiton ids const ID_REGEX = /^(fdo|gtk)\|([^|]+)\|(.*)$/; // A list of known SMS apps const SMS_APPS = [ // Popular apps that don't contain the string 'sms' 'com.android.messaging', // AOSP 'com.google.android.apps.messaging', // Google Messages 'com.textra', // Textra 'xyz.klinker.messenger', // Pulse 'com.calea.echo', // Mood Messenger 'com.moez.QKSMS', // QKSMS 'rpkandrodev.yaata', // YAATA 'com.tencent.mm', // WeChat 'com.viber.voip', // Viber 'com.kakao.talk', // KakaoTalk 'com.concentriclivers.mms.com.android.mms', // AOSP Clone 'fr.slvn.mms', // AOSP Clone 'com.promessage.message', // 'com.htc.sense.mms', // HTC Messages // Known not to work with sms plugin 'org.thoughtcrime.securesms', // Signal Private Messenger 'com.samsung.android.messaging', // Samsung Messages ]; /** * Try to determine if an notification is from an SMS app * * @param {Core.Packet} packet - A `kdeconnect.notification` * @return {boolean} Whether the notification is from an SMS app */ function _isSmsNotification(packet) { const id = packet.body.id; if (id.includes('sms')) return true; for (let i = 0, len = SMS_APPS.length; i < len; i++) { if (id.includes(SMS_APPS[i])) return true; } return false; } /** * Remove a local libnotify or Gtk notification. * * @param {String|Number} id - Gtk (string) or libnotify id (uint32) * @param {String|null} application - Application Id if Gtk or null */ function _removeNotification(id, application = null) { let name, path, method, variant; if (application !== null) { name = 'org.gtk.Notifications'; method = 'RemoveNotification'; path = '/org/gtk/Notifications'; variant = new GLib.Variant('(ss)', [application, id]); } else { name = 'org.freedesktop.Notifications'; path = '/org/freedesktop/Notifications'; method = 'CloseNotification'; variant = new GLib.Variant('(u)', [id]); } Gio.DBus.session.call( name, path, name, method, variant, null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { connection.call_finish(res); } catch (e) { logError(e); } } ); } /** * Notification Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/notifications * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/sendnotifications */ const NotificationPlugin = GObject.registerClass({ GTypeName: 'GSConnectNotificationPlugin', }, class NotificationPlugin extends Plugin { _init(device) { super._init(device, 'notification'); this._listener = Components.acquire('notification'); this._session = Components.acquire('session'); this._notificationAddedId = this._listener.connect( 'notification-added', this._onNotificationAdded.bind(this) ); // Load application notification settings this._applicationsChangedId = this.settings.connect( 'changed::applications', this._onApplicationsChanged.bind(this) ); this._onApplicationsChanged(this.settings, 'applications'); this._applicationsChangedSkip = false; } connected() { super.connected(); this._requestNotifications(); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.notification': this._handleNotification(packet); break; // TODO case 'kdeconnect.notification.action': this._handleNotificationAction(packet); break; // No Linux/BSD desktop notifications are repliable as yet case 'kdeconnect.notification.reply': debug(`Not implemented: ${packet.type}`); break; case 'kdeconnect.notification.request': this._handleNotificationRequest(packet); break; default: debug(`Unknown notification packet: ${packet.type}`); } } _onApplicationsChanged(settings, key) { if (this._applicationsChangedSkip) return; try { const json = settings.get_string(key); this._applications = JSON.parse(json); } catch (e) { debug(e, this.device.name); this._applicationsChangedSkip = true; settings.set_string(key, '{}'); this._applicationsChangedSkip = false; } } _onNotificationAdded(listener, notification) { try { const notif = notification.full_unpack(); // An unconfigured application if (notif.appName && !this._applications[notif.appName]) { this._applications[notif.appName] = { iconName: 'system-run-symbolic', enabled: true, }; // Store the themed icons for the device preferences window if (notif.icon === undefined) { // Keep default } else if (typeof notif.icon === 'string') { this._applications[notif.appName].iconName = notif.icon; } else if (notif.icon instanceof Gio.ThemedIcon) { const iconName = notif.icon.get_names()[0]; this._applications[notif.appName].iconName = iconName; } this._applicationsChangedSkip = true; this.settings.set_string( 'applications', JSON.stringify(this._applications) ); this._applicationsChangedSkip = false; } // Sending notifications forbidden if (!this.settings.get_boolean('send-notifications')) return; // Sending when the session is active is forbidden if (!this.settings.get_boolean('send-active') && this._session.active) return; // Notifications disabled for this application if (notif.appName && !this._applications[notif.appName].enabled) return; this.sendNotification(notif); } catch (e) { debug(e, this.device.name); } } /** * Handle an incoming notification or closed report. * * FIXME: upstream kdeconnect-android is tagging many notifications as * `silent`, causing them to never be shown. Since we already handle * duplicates in the Shell, we ignore that flag for now. * * @param {Core.Packet} packet - A `kdeconnect.notification` */ _handleNotification(packet) { // A report that a remote notification has been dismissed if (packet.body.hasOwnProperty('isCancel')) this.device.hideNotification(packet.body.id); // A normal, remote notification else this._receiveNotification(packet); } /** * Handle an incoming request to activate a notification action. * * @param {Core.Packet} packet - A `kdeconnect.notification.action` */ _handleNotificationAction(packet) { throw new GObject.NotImplementedError(); } /** * Handle an incoming request to close or list notifications. * * @param {Core.Packet} packet - A `kdeconnect.notification.request` */ _handleNotificationRequest(packet) { // A request for our notifications. This isn't implemented and would be // pretty hard to without communicating with GNOME Shell. if (packet.body.hasOwnProperty('request')) return; // A request to close a local notification // // TODO: kdeconnect-android doesn't send these, and will instead send a // kdeconnect.notification packet with isCancel and an id of "0". // // For clients that do support it, we report notification ids in the // form "type|application-id|notification-id" so we can close it with // the appropriate service. if (packet.body.hasOwnProperty('cancel')) { const [, type, application, id] = ID_REGEX.exec(packet.body.cancel); if (type === 'fdo') _removeNotification(parseInt(id)); else if (type === 'gtk') _removeNotification(id, application); } } /** * Upload an icon from a GLib.Bytes object. * * @param {Core.Packet} packet - The packet for the notification * @param {GLib.Bytes} bytes - The icon bytes */ _uploadBytesIcon(packet, bytes) { const stream = Gio.MemoryInputStream.new_from_bytes(bytes); this._uploadIconStream(packet, stream, bytes.get_size()); } /** * Upload an icon from a Gio.File object. * * @param {Core.Packet} packet - A `kdeconnect.notification` * @param {Gio.File} file - A file object for the icon */ async _uploadFileIcon(packet, file) { const read = file.read_async(GLib.PRIORITY_DEFAULT, null); const query = file.query_info_async('standard::size', Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null); const [stream, info] = await Promise.all([read, query]); this._uploadIconStream(packet, stream, info.get_size()); } /** * A function for uploading GThemedIcons * * @param {Core.Packet} packet - The packet for the notification * @param {Gio.ThemedIcon} icon - The GIcon to upload */ _uploadThemedIcon(packet, icon) { const theme = Gtk.IconTheme.get_default(); let file = null; for (const name of icon.names) { // NOTE: kdeconnect-android doesn't support SVGs const size = Math.max.apply(null, theme.get_icon_sizes(name)); const info = theme.lookup_icon(name, size, Gtk.IconLookupFlags.NO_SVG); // Send the first icon we find from the options if (info) { file = Gio.File.new_for_path(info.get_filename()); break; } } if (file) this._uploadFileIcon(packet, file); else this.device.sendPacket(packet); } /** * All icon types end up being uploaded in this function. * * @param {Core.Packet} packet - The packet for the notification * @param {Gio.InputStream} stream - A stream to read the icon bytes from * @param {number} size - Size of the icon in bytes */ async _uploadIconStream(packet, stream, size) { try { const transfer = this.device.createTransfer(); transfer.addStream(packet, stream, size); await transfer.start(); } catch (e) { debug(e); this.device.sendPacket(packet); } } /** * Upload an icon from a GIcon or themed icon name. * * @param {Core.Packet} packet - A `kdeconnect.notification` * @param {Gio.Icon|string|null} icon - An icon or %null * @return {Promise} A promise for the operation */ _uploadIcon(packet, icon = null) { // Normalize strings into GIcons if (typeof icon === 'string') icon = Gio.Icon.new_for_string(icon); if (icon instanceof Gio.ThemedIcon) return this._uploadThemedIcon(packet, icon); if (icon instanceof Gio.FileIcon) return this._uploadFileIcon(packet, icon.get_file()); if (icon instanceof Gio.BytesIcon) return this._uploadBytesIcon(packet, icon.get_bytes()); return this.device.sendPacket(packet); } /** * Send a local notification to the remote device. * * @param {Object} notif - A dictionary of notification parameters * @param {string} notif.appName - The notifying application * @param {string} notif.id - The notification ID * @param {string} notif.title - The notification title * @param {string} notif.body - The notification body * @param {string} notif.ticker - The notification title & body * @param {boolean} notif.isClearable - If the notification can be closed * @param {string|Gio.Icon} notif.icon - An icon name or GIcon */ async sendNotification(notif) { try { const icon = notif.icon || null; delete notif.icon; await this._uploadIcon({ type: 'kdeconnect.notification', body: notif, }, icon); } catch (e) { logError(e); } } async _downloadIcon(packet) { try { if (!packet.hasPayload()) return null; // Save the file in the global cache const path = GLib.build_filenamev([ Config.CACHEDIR, packet.body.payloadHash || `${Date.now()}`, ]); // Check if we've already downloaded this icon // NOTE: if we reject the transfer kdeconnect-android will resend // the notification packet, which may cause problems wrt #789 const file = Gio.File.new_for_path(path); if (file.query_exists(null)) return new Gio.FileIcon({file: file}); // Open the target path and create a transfer const transfer = this.device.createTransfer(); transfer.addFile(packet, file); try { await transfer.start(); return new Gio.FileIcon({file: file}); } catch (e) { debug(e, this.device.name); file.delete_async(GLib.PRIORITY_DEFAULT, null, null); return null; } } catch (e) { debug(e, this.device.name); return null; } } /** * Receive an incoming notification. * * @param {Core.Packet} packet - A `kdeconnect.notification` */ async _receiveNotification(packet) { try { // Set defaults let action = null; let buttons = []; let id = packet.body.id; let title = packet.body.appName; let body = `${packet.body.title}: ${packet.body.text}`; let icon = await this._downloadIcon(packet); // Repliable Notification if (packet.body.requestReplyId) { id = `${packet.body.id}|${packet.body.requestReplyId}`; action = { name: 'replyNotification', parameter: new GLib.Variant('(ssa{ss})', [ packet.body.requestReplyId, '', { appName: packet.body.appName, title: packet.body.title, text: packet.body.text, }, ]), }; } // Notification Actions if (packet.body.actions) { buttons = packet.body.actions.map(action => { return { label: action, action: 'activateNotification', parameter: new GLib.Variant('(ss)', [id, action]), }; }); } // Special case for Missed Calls if (packet.body.id.includes('MissedCall')) { title = packet.body.title; body = packet.body.text; if (icon === null) icon = new Gio.ThemedIcon({name: 'call-missed-symbolic'}); // Special case for SMS notifications } else if (_isSmsNotification(packet)) { title = packet.body.title; body = packet.body.text; action = { name: 'replySms', parameter: new GLib.Variant('s', packet.body.title), }; if (icon === null) icon = new Gio.ThemedIcon({name: 'sms-symbolic'}); // Special case where 'appName' is the same as 'title' } else if (packet.body.appName === packet.body.title) { body = packet.body.text; } // Use the device icon if we still don't have one if (icon === null) icon = new Gio.ThemedIcon({name: this.device.icon_name}); // Show the notification this.device.showNotification({ id: id, title: title, body: body, icon: icon, action: action, buttons: buttons, }); } catch (e) { logError(e); } } /** * Request the remote notifications be sent */ _requestNotifications() { this.device.sendPacket({ type: 'kdeconnect.notification.request', body: {request: true}, }); } /** * Report that a local notification has been closed/dismissed. * TODO: kdeconnect-android doesn't handle incoming isCancel packets. * * @param {string} id - The local notification id */ withdrawNotification(id) { this.device.sendPacket({ type: 'kdeconnect.notification', body: { isCancel: true, id: id, }, }); } /** * Close a remote notification. * TODO: ignore local notifications * * @param {string} id - The remote notification id */ closeNotification(id) { this.device.sendPacket({ type: 'kdeconnect.notification.request', body: {cancel: id}, }); } /** * Reply to a notification sent with a requestReplyId UUID * * @param {string} uuid - The requestReplyId for the repliable notification * @param {string} message - The message to reply with * @param {Object} notification - The original notification packet */ replyNotification(uuid, message, notification) { // If this happens for some reason, things will explode if (!uuid) throw Error('Missing UUID'); // If the message has no content, open a dialog for the user to add one if (!message) { const dialog = new ReplyDialog({ device: this.device, uuid: uuid, notification: notification, plugin: this, }); dialog.present(); // Otherwise just send the reply } else { this.device.sendPacket({ type: 'kdeconnect.notification.reply', body: { requestReplyId: uuid, message: message, }, }); } } /** * Activate a remote notification action * * @param {string} id - The remote notification id * @param {string} action - The notification action (label) */ activateNotification(id, action) { this.device.sendPacket({ type: 'kdeconnect.notification.action', body: { action: action, key: id, }, }); } destroy() { this.settings.disconnect(this._applicationsChangedId); if (this._listener !== undefined) { this._listener.disconnect(this._notificationAddedId); this._listener = Components.release('notification'); } if (this._session !== undefined) this._session = Components.release('session'); super.destroy(); } }); export default NotificationPlugin; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/ping.js�����������������������0000664�0000000�0000000�00000003464�14607666711�0027256�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Ping'), description: _('Send and receive pings'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Ping', incomingCapabilities: ['kdeconnect.ping'], outgoingCapabilities: ['kdeconnect.ping'], actions: { ping: { label: _('Ping'), icon_name: 'dialog-information-symbolic', parameter_type: new GLib.VariantType('s'), incoming: [], outgoing: ['kdeconnect.ping'], }, }, }; /** * Ping Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/ping */ const PingPlugin = GObject.registerClass({ GTypeName: 'GSConnectPingPlugin', }, class PingPlugin extends Plugin { _init(device) { super._init(device, 'ping'); } handlePacket(packet) { // Notification const notif = { title: this.device.name, body: _('Ping'), icon: new Gio.ThemedIcon({name: `${this.device.icon_name}`}), }; if (packet.body.message) { // TRANSLATORS: An optional message accompanying a ping, rarely if ever used // eg. Ping: A message sent with ping notif.body = _('Ping: %s').format(packet.body.message); } this.device.showNotification(notif); } ping(message = '') { const packet = { type: 'kdeconnect.ping', body: {}, }; if (message.length) packet.body.message = message; this.device.sendPacket(packet); } }); export default PingPlugin; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/presenter.js������������������0000664�0000000�0000000�00000003441�14607666711�0030323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GObject from 'gi://GObject'; import * as Components from '../components/index.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Presentation'), description: _('Use the paired device as a presenter'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Presenter', incomingCapabilities: ['kdeconnect.presenter'], outgoingCapabilities: [], actions: {}, }; /** * Presenter Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/presenter * https://github.com/KDE/kdeconnect-android/tree/master/src/org/kde/kdeconnect/Plugins/PresenterPlugin/ */ const PresenterPlugin = GObject.registerClass({ GTypeName: 'GSConnectPresenterPlugin', }, class PresenterPlugin extends Plugin { _init(device) { super._init(device, 'presenter'); if (!globalThis.HAVE_GNOME) this._input = Components.acquire('ydotool'); else this._input = Components.acquire('input'); } handlePacket(packet) { if (packet.body.hasOwnProperty('dx')) { this._input.movePointer( packet.body.dx * 1000, packet.body.dy * 1000 ); } else if (packet.body.stop) { // Currently unsupported and unnecessary as we just re-use the mouse // pointer instead of showing an arbitrary window. } } destroy() { if (this._input !== undefined) { if (!globalThis.HAVE_GNOME) this._input = Components.release('ydotool'); else this._input = Components.release('input'); } super.destroy(); } }); export default PresenterPlugin; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/runcommand.js�����������������0000664�0000000�0000000�00000016500�14607666711�0030457�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Run Commands'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.RunCommand', description: _('Run commands on your paired device or let the device run predefined commands on this PC'), incomingCapabilities: [ 'kdeconnect.runcommand', 'kdeconnect.runcommand.request', ], outgoingCapabilities: [ 'kdeconnect.runcommand', 'kdeconnect.runcommand.request', ], actions: { commands: { label: _('Commands'), icon_name: 'system-run-symbolic', parameter_type: new GLib.VariantType('s'), incoming: ['kdeconnect.runcommand'], outgoing: ['kdeconnect.runcommand.request'], }, executeCommand: { label: _('Commands'), icon_name: 'system-run-symbolic', parameter_type: new GLib.VariantType('s'), incoming: ['kdeconnect.runcommand'], outgoing: ['kdeconnect.runcommand.request'], }, }, }; /** * RunCommand Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/remotecommands * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/runcommand */ const RunCommandPlugin = GObject.registerClass({ GTypeName: 'GSConnectRunCommandPlugin', Properties: { 'remote-commands': GObject.param_spec_variant( 'remote-commands', 'Remote Command List', 'A list of the device\'s remote commands', new GLib.VariantType('a{sv}'), null, GObject.ParamFlags.READABLE ), }, }, class RunCommandPlugin extends Plugin { _init(device) { super._init(device, 'runcommand'); // Local Commands this._commandListChangedId = this.settings.connect( 'changed::command-list', this._sendCommandList.bind(this) ); // We cache remote commands so they can be used in the settings even // when the device is offline. this._remote_commands = {}; this.cacheProperties(['_remote_commands']); } get remote_commands() { return this._remote_commands; } connected() { super.connected(); this._sendCommandList(); this._requestCommandList(); this._handleCommandList(this.remote_commands); } clearCache() { this._remote_commands = {}; this.notify('remote-commands'); } cacheLoaded() { if (!this.device.connected) return; this._sendCommandList(); this._requestCommandList(); this._handleCommandList(this.remote_commands); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.runcommand': this._handleCommandList(packet.body.commandList); break; case 'kdeconnect.runcommand.request': if (packet.body.hasOwnProperty('key')) this._handleCommand(packet.body.key); else if (packet.body.hasOwnProperty('requestCommandList')) this._sendCommandList(); break; } } /** * Handle a request to execute the local command with the UUID @key * * @param {string} key - The UUID of the local command */ _handleCommand(key) { try { const commands = this.settings.get_value('command-list'); const commandList = commands.recursiveUnpack(); if (!commandList.hasOwnProperty(key)) { throw new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.PERMISSION_DENIED, message: `Unknown command: ${key}`, }); } this.device.launchProcess([ '/bin/sh', '-c', commandList[key].command, ]); } catch (e) { logError(e, this.device.name); } } /** * Parse the response to a request for the remote command list. Remove the * command menu if there are no commands, otherwise amend the menu. * * @param {string|Object[]} commandList - A list of remote commands */ _handleCommandList(commandList) { // See: https://github.com/GSConnect/gnome-shell-extension-gsconnect/issues/1051 if (typeof commandList === 'string') { try { commandList = JSON.parse(commandList); } catch (e) { commandList = {}; } } this._remote_commands = commandList; this.notify('remote-commands'); const commandEntries = Object.entries(this.remote_commands); // If there are no commands, hide the menu by disabling the action this.device.lookup_action('commands').enabled = (commandEntries.length > 0); // Commands Submenu const submenu = new Gio.Menu(); for (const [uuid, info] of commandEntries) { const item = new Gio.MenuItem(); item.set_label(info.name); item.set_icon( new Gio.ThemedIcon({name: 'application-x-executable-symbolic'}) ); item.set_detailed_action(`device.executeCommand::${uuid}`); submenu.append_item(item); } // Commands Item const item = new Gio.MenuItem(); item.set_detailed_action('device.commands::menu'); item.set_attribute_value( 'hidden-when', new GLib.Variant('s', 'action-disabled') ); item.set_icon(new Gio.ThemedIcon({name: 'system-run-symbolic'})); item.set_label(_('Commands')); item.set_submenu(submenu); // If the submenu item is already present it will be replaced const menuActions = this.device.settings.get_strv('menu-actions'); const index = menuActions.indexOf('commands'); if (index > -1) { this.device.removeMenuAction('device.commands'); this.device.addMenuItem(item, index); } } /** * Send a request for the remote command list */ _requestCommandList() { this.device.sendPacket({ type: 'kdeconnect.runcommand.request', body: {requestCommandList: true}, }); } /** * Send the local command list */ _sendCommandList() { const commands = this.settings.get_value('command-list').recursiveUnpack(); const commandList = JSON.stringify(commands); this.device.sendPacket({ type: 'kdeconnect.runcommand', body: {commandList: commandList}, }); } /** * Placeholder function for command action */ commands() {} /** * Send a request to execute the remote command with the UUID @key * * @param {string} key - The UUID of the remote command */ executeCommand(key) { this.device.sendPacket({ type: 'kdeconnect.runcommand.request', body: {key: key}, }); } destroy() { this.settings.disconnect(this._commandListChangedId); super.destroy(); } }); export default RunCommandPlugin; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/sftp.js�����������������������0000664�0000000�0000000�00000035143�14607666711�0027274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Config from '../../config.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('SFTP'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.SFTP', description: _('Browse the paired device filesystem'), incomingCapabilities: ['kdeconnect.sftp'], outgoingCapabilities: ['kdeconnect.sftp.request'], actions: { mount: { label: _('Mount'), icon_name: 'folder-remote-symbolic', parameter_type: null, incoming: ['kdeconnect.sftp'], outgoing: ['kdeconnect.sftp.request'], }, unmount: { label: _('Unmount'), icon_name: 'media-eject-symbolic', parameter_type: null, incoming: ['kdeconnect.sftp'], outgoing: ['kdeconnect.sftp.request'], }, }, }; const MAX_MOUNT_DIRS = 12; /** * SFTP Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/sftp * https://github.com/KDE/kdeconnect-android/tree/master/src/org/kde/kdeconnect/Plugins/SftpPlugin */ const SFTPPlugin = GObject.registerClass({ GTypeName: 'GSConnectSFTPPlugin', }, class SFTPPlugin extends Plugin { _init(device) { super._init(device, 'sftp'); this._gmount = null; this._mounting = false; // A reusable launcher for ssh processes this._launcher = new Gio.SubprocessLauncher({ flags: (Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_MERGE), }); // Watch the volume monitor this._volumeMonitor = Gio.VolumeMonitor.get(); this._mountAddedId = this._volumeMonitor.connect( 'mount-added', this._onMountAdded.bind(this) ); this._mountRemovedId = this._volumeMonitor.connect( 'mount-removed', this._onMountRemoved.bind(this) ); } get gmount() { if (this._gmount === null && this.device.connected) { const host = this.device.channel.host; const regex = new RegExp( `sftp://(${host}):(1739|17[4-5][0-9]|176[0-4])` ); for (const mount of this._volumeMonitor.get_mounts()) { const uri = mount.get_root().get_uri(); if (regex.test(uri)) { this._gmount = mount; this._addSubmenu(mount); this._addSymlink(mount); break; } } } return this._gmount; } connected() { super.connected(); // Only enable for Lan connections if (this.device.channel.constructor.name === 'LanChannel') { // FIXME: Circular import workaround if (this.settings.get_boolean('automount')) this.mount(); } else { this.device.lookup_action('mount').enabled = false; this.device.lookup_action('unmount').enabled = false; } } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.sftp': if (packet.body.hasOwnProperty('errorMessage')) this._handleError(packet); else this._handleMount(packet); break; } } _onMountAdded(monitor, mount) { if (this._gmount !== null || !this.device.connected) return; const host = this.device.channel.host; const regex = new RegExp(`sftp://(${host}):(1739|17[4-5][0-9]|176[0-4])`); const uri = mount.get_root().get_uri(); if (!regex.test(uri)) return; this._gmount = mount; this._addSubmenu(mount); this._addSymlink(mount); } _onMountRemoved(monitor, mount) { if (this.gmount !== mount) return; this._gmount = null; this._removeSubmenu(); } async _listDirectories(mount) { const file = mount.get_root(); const iter = await file.enumerate_children_async( Gio.FILE_ATTRIBUTE_STANDARD_NAME, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, GLib.PRIORITY_DEFAULT, this.cancellable); const infos = await iter.next_files_async(MAX_MOUNT_DIRS, GLib.PRIORITY_DEFAULT, this.cancellable); iter.close_async(GLib.PRIORITY_DEFAULT, null, null); const directories = {}; for (const info of infos) { const name = info.get_name(); directories[name] = `${file.get_uri()}${name}/`; } return directories; } _onAskQuestion(op, message, choices) { op.reply(Gio.MountOperationResult.HANDLED); } _onAskPassword(op, message, user, domain, flags) { op.reply(Gio.MountOperationResult.HANDLED); } /** * Handle an error reported by the remote device. * * @param {Core.Packet} packet - a `kdeconnect.sftp` */ _handleError(packet) { this.device.showNotification({ id: 'sftp-error', title: _('%s reported an error').format(this.device.name), body: packet.body.errorMessage, icon: new Gio.ThemedIcon({name: 'dialog-error-symbolic'}), priority: Gio.NotificationPriority.HIGH, }); } /** * Mount the remote device using the provided information. * * @param {Core.Packet} packet - a `kdeconnect.sftp` */ async _handleMount(packet) { try { // Already mounted or mounting if (this.gmount !== null || this._mounting) return; this._mounting = true; // Ensure the private key is in the keyring await this._addPrivateKey(); // Create a new mount operation const op = new Gio.MountOperation({ username: packet.body.user || null, password: packet.body.password || null, password_save: Gio.PasswordSave.NEVER, }); op.connect('ask-question', this._onAskQuestion); op.connect('ask-password', this._onAskPassword); // This is the actual call to mount the device const host = this.device.channel.host; const uri = `sftp://${host}:${packet.body.port}/`; const file = Gio.File.new_for_uri(uri); await file.mount_enclosing_volume(GLib.PRIORITY_DEFAULT, op, this.cancellable); } catch (e) { // Special case when the GMount didn't unmount properly but is still // on the same port and can be reused. if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.ALREADY_MOUNTED)) return; // There's a good chance this is a host key verification error; // regardless we'll remove the key for security. this._removeHostKey(this.device.channel.host); logError(e, this.device.name); } finally { this._mounting = false; } } /** * Add GSConnect's private key identity to the authentication agent so our * identity can be verified by Android during private key authentication. * * @return {Promise} A promise for the operation */ async _addPrivateKey() { const ssh_add = this._launcher.spawnv([ Config.SSHADD_PATH, GLib.build_filenamev([Config.CONFIGDIR, 'private.pem']), ]); const [stdout] = await ssh_add.communicate_utf8_async(null, this.cancellable); if (ssh_add.get_exit_status() !== 0) debug(stdout.trim(), this.device.name); } /** * Remove all host keys from ~/.ssh/known_hosts for @host in the port range * used by KDE Connect (1739-1764). * * @param {string} host - A hostname or IP address */ async _removeHostKey(host) { for (let port = 1739; port <= 1764; port++) { try { const ssh_keygen = this._launcher.spawnv([ Config.SSHKEYGEN_PATH, '-R', `[${host}]:${port}`, ]); const [stdout] = await ssh_keygen.communicate_utf8_async(null, this.cancellable); const status = ssh_keygen.get_exit_status(); if (status !== 0) { throw new Gio.IOErrorEnum({ code: Gio.io_error_from_errno(status), message: `${GLib.strerror(status)}\n${stdout}`.trim(), }); } } catch (e) { logError(e, this.device.name); } } } /* * Mount menu helpers */ _getUnmountSection() { if (this._unmountSection === undefined) { this._unmountSection = new Gio.Menu(); const unmountItem = new Gio.MenuItem(); unmountItem.set_label(Metadata.actions.unmount.label); unmountItem.set_icon(new Gio.ThemedIcon({ name: Metadata.actions.unmount.icon_name, })); unmountItem.set_detailed_action('device.unmount'); this._unmountSection.append_item(unmountItem); } return this._unmountSection; } _getFilesMenuItem() { if (this._filesMenuItem === undefined) { // Files menu icon const emblem = new Gio.Emblem({ icon: new Gio.ThemedIcon({name: 'emblem-default'}), }); const mountedIcon = new Gio.EmblemedIcon({ gicon: new Gio.ThemedIcon({name: 'folder-remote-symbolic'}), }); mountedIcon.add_emblem(emblem); // Files menu item this._filesMenuItem = new Gio.MenuItem(); this._filesMenuItem.set_detailed_action('device.mount'); this._filesMenuItem.set_icon(mountedIcon); this._filesMenuItem.set_label(_('Files')); } return this._filesMenuItem; } async _addSubmenu(mount) { try { const directories = await this._listDirectories(mount); // Submenu sections const dirSection = new Gio.Menu(); const unmountSection = this._getUnmountSection(); for (const [name, uri] of Object.entries(directories)) dirSection.append(name, `device.openPath::${uri}`); // Files submenu const filesSubmenu = new Gio.Menu(); filesSubmenu.append_section(null, dirSection); filesSubmenu.append_section(null, unmountSection); // Files menu item const filesMenuItem = this._getFilesMenuItem(); filesMenuItem.set_submenu(filesSubmenu); // Replace the existing menu item const index = this.device.removeMenuAction('device.mount'); this.device.addMenuItem(filesMenuItem, index); } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) debug(e, this.device.name); // Reset to allow retrying this._gmount = null; } } _removeSubmenu() { try { const index = this.device.removeMenuAction('device.mount'); const action = this.device.lookup_action('mount'); if (action !== null) { this.device.addMenuAction( action, index, Metadata.actions.mount.label, Metadata.actions.mount.icon_name ); } } catch (e) { logError(e, this.device.name); } } /** * Create a symbolic link referring to the device by name * * @param {Gio.Mount} mount - A GMount to link to */ async _addSymlink(mount) { try { const by_name_dir = Gio.File.new_for_path( `${Config.RUNTIMEDIR}/by-name/` ); try { by_name_dir.make_directory_with_parents(null); } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS)) throw e; } // Replace path separator with a Unicode lookalike: let safe_device_name = this.device.name.replace('/', '∕'); if (safe_device_name === '.') safe_device_name = '·'; else if (safe_device_name === '..') safe_device_name = '··'; const link_target = mount.get_root().get_path(); const link = Gio.File.new_for_path( `${by_name_dir.get_path()}/${safe_device_name}`); // Check for and remove any existing stale link try { const link_stat = await link.query_info_async( 'standard::symlink-target', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, GLib.PRIORITY_DEFAULT, this.cancellable); if (link_stat.get_symlink_target() === link_target) return; await link.delete_async(GLib.PRIORITY_DEFAULT, this.cancellable); } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) throw e; } link.make_symbolic_link(link_target, this.cancellable); } catch (e) { debug(e, this.device.name); } } /** * Send a request to mount the remote device */ mount() { if (this.gmount !== null) return; this.device.sendPacket({ type: 'kdeconnect.sftp.request', body: { startBrowsing: true, }, }); } /** * Remove the menu items, unmount the filesystem, replace the mount item */ async unmount() { try { if (this.gmount === null) return; this._removeSubmenu(); this._mounting = false; await this.gmount.unmount_with_operation( Gio.MountUnmountFlags.FORCE, new Gio.MountOperation(), this.cancellable); } catch (e) { debug(e, this.device.name); } } destroy() { if (this._volumeMonitor) { this._volumeMonitor.disconnect(this._mountAddedId); this._volumeMonitor.disconnect(this._mountRemovedId); this._volumeMonitor = null; } super.destroy(); } }); export default SFTPPlugin; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/share.js����������������������0000664�0000000�0000000�00000037424�14607666711�0027426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GdkPixbuf from 'gi://GdkPixbuf'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import Plugin from '../plugin.js'; import * as URI from '../utils/uri.js'; export const Metadata = { label: _('Share'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Share', description: _('Share files and URLs between devices'), incomingCapabilities: ['kdeconnect.share.request'], outgoingCapabilities: ['kdeconnect.share.request'], actions: { share: { label: _('Share'), icon_name: 'send-to-symbolic', parameter_type: null, incoming: [], outgoing: ['kdeconnect.share.request'], }, shareFile: { label: _('Share File'), icon_name: 'document-send-symbolic', parameter_type: new GLib.VariantType('(sb)'), incoming: [], outgoing: ['kdeconnect.share.request'], }, shareText: { label: _('Share Text'), icon_name: 'send-to-symbolic', parameter_type: new GLib.VariantType('s'), incoming: [], outgoing: ['kdeconnect.share.request'], }, shareUri: { label: _('Share Link'), icon_name: 'send-to-symbolic', parameter_type: new GLib.VariantType('s'), incoming: [], outgoing: ['kdeconnect.share.request'], }, }, }; /** * Share Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/share * * TODO: receiving 'text' TODO: Window with textview & 'Copy to Clipboard.. * https://github.com/KDE/kdeconnect-kde/commit/28f11bd5c9a717fb9fbb3f02ddd6cea62021d055 */ const SharePlugin = GObject.registerClass({ GTypeName: 'GSConnectSharePlugin', }, class SharePlugin extends Plugin { _init(device) { super._init(device, 'share'); } handlePacket(packet) { // TODO: composite jobs (lastModified, numberOfFiles, totalPayloadSize) if (packet.body.hasOwnProperty('filename')) { if (this.settings.get_boolean('receive-files')) this._handleFile(packet); else this._refuseFile(packet); } else if (packet.body.hasOwnProperty('text')) { this._handleText(packet); } else if (packet.body.hasOwnProperty('url')) { this._handleUri(packet); } } _ensureReceiveDirectory() { let receiveDir = this.settings.get_string('receive-directory'); // Ensure a directory is set if (receiveDir.length === 0) { receiveDir = GLib.get_user_special_dir( GLib.UserDirectory.DIRECTORY_DOWNLOAD ); // Fallback to ~/Downloads const homeDir = GLib.get_home_dir(); if (!receiveDir || receiveDir === homeDir) receiveDir = GLib.build_filenamev([homeDir, 'Downloads']); this.settings.set_string('receive-directory', receiveDir); } // Ensure the directory exists if (!GLib.file_test(receiveDir, GLib.FileTest.IS_DIR)) GLib.mkdir_with_parents(receiveDir, 448); return receiveDir; } _getFile(filename) { const dirpath = this._ensureReceiveDirectory(); const basepath = GLib.build_filenamev([dirpath, filename]); let filepath = basepath; let copyNum = 0; while (GLib.file_test(filepath, GLib.FileTest.EXISTS)) filepath = `${basepath} (${++copyNum})`; return Gio.File.new_for_path(filepath); } _refuseFile(packet) { try { this.device.rejectTransfer(packet); this.device.showNotification({ id: `${Date.now()}`, title: _('Transfer Failed'), // TRANSLATORS: eg. Google Pixel is not allowed to upload files body: _('%s is not allowed to upload files').format( this.device.name ), icon: new Gio.ThemedIcon({name: 'dialog-error-symbolic'}), }); } catch (e) { debug(e, this.device.name); } } async _handleFile(packet) { try { const file = this._getFile(packet.body.filename); // Create the transfer const transfer = this.device.createTransfer(); transfer.addFile(packet, file); // Notify that we're about to start the transfer this.device.showNotification({ id: transfer.uuid, title: _('Transferring File'), // TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel body: _('Receiving “%s†from %s').format( packet.body.filename, this.device.name ), buttons: [{ label: _('Cancel'), action: 'cancelTransfer', parameter: new GLib.Variant('s', transfer.uuid), }], icon: new Gio.ThemedIcon({name: 'document-save-symbolic'}), }); // We'll show a notification (success or failure) let title, body, action, iconName; let buttons = []; try { await transfer.start(); title = _('Transfer Successful'); // TRANSLATORS: eg. Received 'book.pdf' from Google Pixel body = _('Received “%s†from %s').format( packet.body.filename, this.device.name ); action = { name: 'showPathInFolder', parameter: new GLib.Variant('s', file.get_uri()), }; buttons = [ { label: _('Show File Location'), action: 'showPathInFolder', parameter: new GLib.Variant('s', file.get_uri()), }, { label: _('Open File'), action: 'openPath', parameter: new GLib.Variant('s', file.get_uri()), }, ]; iconName = 'document-save-symbolic'; if (packet.body.open) { const uri = file.get_uri(); Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null); } } catch (e) { debug(e, this.device.name); title = _('Transfer Failed'); // TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel body = _('Failed to receive “%s†from %s').format( packet.body.filename, this.device.name ); iconName = 'dialog-warning-symbolic'; // Clean up the downloaded file on failure file.delete_async(GLib.PRIORITY_DEAFAULT, null, null); } this.device.hideNotification(transfer.uuid); this.device.showNotification({ id: transfer.uuid, title: title, body: body, action: action, buttons: buttons, icon: new Gio.ThemedIcon({name: iconName}), }); } catch (e) { logError(e, this.device.name); } } _handleUri(packet) { const uri = packet.body.url; Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null); } _handleText(packet) { const dialog = new Gtk.MessageDialog({ text: _('Text Shared By %s').format(this.device.name), secondary_text: URI.linkify(packet.body.text), secondary_use_markup: true, buttons: Gtk.ButtonsType.CLOSE, }); dialog.message_area.get_children()[1].selectable = true; dialog.set_keep_above(true); dialog.connect('response', (dialog) => dialog.destroy()); dialog.show(); } /** * Open the file chooser dialog for selecting a file or inputing a URI. */ share() { const dialog = new FileChooserDialog(this.device); dialog.show(); } /** * Share local file path or URI * * @param {string} path - Local file path or URI * @param {boolean} open - Whether the file should be opened after transfer */ async shareFile(path, open = false) { try { let file = null; if (path.includes('://')) file = Gio.File.new_for_uri(path); else file = Gio.File.new_for_path(path); // Create the transfer const transfer = this.device.createTransfer(); transfer.addFile({ type: 'kdeconnect.share.request', body: { filename: file.get_basename(), open: open, }, }, file); // Notify that we're about to start the transfer this.device.showNotification({ id: transfer.uuid, title: _('Transferring File'), // TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel body: _('Sending “%s†to %s').format( file.get_basename(), this.device.name ), buttons: [{ label: _('Cancel'), action: 'cancelTransfer', parameter: new GLib.Variant('s', transfer.uuid), }], icon: new Gio.ThemedIcon({name: 'document-send-symbolic'}), }); // We'll show a notification (success or failure) let title, body, iconName; try { await transfer.start(); title = _('Transfer Successful'); // TRANSLATORS: eg. Sent "book.pdf" to Google Pixel body = _('Sent “%s†to %s').format( file.get_basename(), this.device.name ); iconName = 'document-send-symbolic'; } catch (e) { debug(e, this.device.name); title = _('Transfer Failed'); // TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel body = _('Failed to send “%s†to %s').format( file.get_basename(), this.device.name ); iconName = 'dialog-warning-symbolic'; } this.device.hideNotification(transfer.uuid); this.device.showNotification({ id: transfer.uuid, title: title, body: body, icon: new Gio.ThemedIcon({name: iconName}), }); } catch (e) { debug(e, this.device.name); } } /** * Share a string of text. Remote behaviour is undefined. * * @param {string} text - A string of unicode text */ shareText(text) { this.device.sendPacket({ type: 'kdeconnect.share.request', body: {text: text}, }); } /** * Share a URI. Generally the remote device opens it with the scheme default * * @param {string} uri - A URI to share */ shareUri(uri) { if (GLib.uri_parse_scheme(uri) === 'file') { this.shareFile(uri); return; } this.device.sendPacket({ type: 'kdeconnect.share.request', body: {url: uri}, }); } }); /** A simple FileChooserDialog for sharing files */ const FileChooserDialog = GObject.registerClass({ GTypeName: 'GSConnectShareFileChooserDialog', }, class FileChooserDialog extends Gtk.FileChooserDialog { _init(device) { super._init({ // TRANSLATORS: eg. Send files to Google Pixel title: _('Send files to %s').format(device.name), select_multiple: true, extra_widget: new Gtk.CheckButton({ // TRANSLATORS: Mark the file to be opened once completed label: _('Open when done'), visible: true, }), use_preview_label: false, }); this.device = device; // Align checkbox with sidebar const box = this.get_content_area().get_children()[0].get_children()[0]; const paned = box.get_children()[0]; paned.bind_property( 'position', this.extra_widget, 'margin-left', GObject.BindingFlags.SYNC_CREATE ); // Preview Widget this.preview_widget = new Gtk.Image(); this.preview_widget_active = false; this.connect('update-preview', this._onUpdatePreview); // URI entry this._uriEntry = new Gtk.Entry({ placeholder_text: 'https://', hexpand: true, visible: true, }); this._uriEntry.connect('activate', this._sendLink.bind(this)); // URI/File toggle this._uriButton = new Gtk.ToggleButton({ image: new Gtk.Image({ icon_name: 'web-browser-symbolic', pixel_size: 16, }), valign: Gtk.Align.CENTER, // TRANSLATORS: eg. Send a link to Google Pixel tooltip_text: _('Send a link to %s').format(device.name), visible: true, }); this._uriButton.connect('toggled', this._onUriButtonToggled.bind(this)); this.add_button(_('Cancel'), Gtk.ResponseType.CANCEL); const sendButton = this.add_button(_('Send'), Gtk.ResponseType.OK); sendButton.connect('clicked', this._sendLink.bind(this)); this.get_header_bar().pack_end(this._uriButton); this.set_default_response(Gtk.ResponseType.OK); } _onUpdatePreview(chooser) { try { const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( chooser.get_preview_filename(), chooser.get_scale_factor() * 128, -1 ); chooser.preview_widget.pixbuf = pixbuf; chooser.preview_widget.visible = true; chooser.preview_widget_active = true; } catch (e) { chooser.preview_widget.visible = false; chooser.preview_widget_active = false; } } _onUriButtonToggled(button) { const header = this.get_header_bar(); // Show the URL entry if (button.active) { this.extra_widget.sensitive = false; header.set_custom_title(this._uriEntry); this.set_response_sensitive(Gtk.ResponseType.OK, true); // Hide the URL entry } else { header.set_custom_title(null); this.set_response_sensitive( Gtk.ResponseType.OK, this.get_uris().length > 1 ); this.extra_widget.sensitive = true; } } _sendLink(widget) { if (this._uriButton.active && this._uriEntry.text.length) this.response(1); } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { for (const uri of this.get_uris()) { const parameter = new GLib.Variant( '(sb)', [uri, this.extra_widget.active] ); this.device.activate_action('shareFile', parameter); } } else if (response_id === 1) { const parameter = new GLib.Variant('s', this._uriEntry.text); this.device.activate_action('shareUri', parameter); } this.destroy(); } }); export default SharePlugin; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/sms.js������������������������0000664�0000000�0000000�00000036305�14607666711�0027123�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Plugin from '../plugin.js'; import LegacyMessagingDialog from '../ui/legacyMessaging.js'; import * as Messaging from '../ui/messaging.js'; import SmsURI from '../utils/uri.js'; export const Metadata = { label: _('SMS'), description: _('Send and read SMS of the paired device and be notified of new SMS'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.SMS', incomingCapabilities: [ 'kdeconnect.sms.messages', ], outgoingCapabilities: [ 'kdeconnect.sms.request', 'kdeconnect.sms.request_conversation', 'kdeconnect.sms.request_conversations', ], actions: { // SMS Actions sms: { label: _('Messaging'), icon_name: 'sms-symbolic', parameter_type: null, incoming: [], outgoing: ['kdeconnect.sms.request'], }, uriSms: { label: _('New SMS (URI)'), icon_name: 'sms-symbolic', parameter_type: new GLib.VariantType('s'), incoming: [], outgoing: ['kdeconnect.sms.request'], }, replySms: { label: _('Reply SMS'), icon_name: 'sms-symbolic', parameter_type: new GLib.VariantType('s'), incoming: [], outgoing: ['kdeconnect.sms.request'], }, sendMessage: { label: _('Send Message'), icon_name: 'sms-send', parameter_type: new GLib.VariantType('(aa{sv})'), incoming: [], outgoing: ['kdeconnect.sms.request'], }, sendSms: { label: _('Send SMS'), icon_name: 'sms-send', parameter_type: new GLib.VariantType('(ss)'), incoming: [], outgoing: ['kdeconnect.sms.request'], }, shareSms: { label: _('Share SMS'), icon_name: 'sms-send', parameter_type: new GLib.VariantType('s'), incoming: [], outgoing: ['kdeconnect.sms.request'], }, }, }; /** * SMS Message event type. Currently all events are TEXT_MESSAGE. * * TEXT_MESSAGE: Has a "body" field which contains pure, human-readable text */ export const MessageEventType = { TEXT_MESSAGE: 0x1, }; /** * SMS Message status. READ/UNREAD match the 'read' field from the Android App * message packet. * * UNREAD: A message not marked as read * READ: A message marked as read */ export const MessageStatus = { UNREAD: 0, READ: 1, }; /** * SMS Message type, set from the 'type' field in the Android App * message packet. * * See: https://developer.android.com/reference/android/provider/Telephony.TextBasedSmsColumns.html * * ALL: all messages * INBOX: Received messages * SENT: Sent messages * DRAFT: Message drafts * OUTBOX: Outgoing messages * FAILED: Failed outgoing messages * QUEUED: Messages queued to send later */ export const MessageBox = { ALL: 0, INBOX: 1, SENT: 2, DRAFT: 3, OUTBOX: 4, FAILED: 5, QUEUED: 6, }; /** * SMS Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/sms * https://github.com/KDE/kdeconnect-android/tree/master/src/org/kde/kdeconnect/Plugins/SMSPlugin/ */ const SMSPlugin = GObject.registerClass({ GTypeName: 'GSConnectSMSPlugin', Properties: { 'threads': GObject.param_spec_variant( 'threads', 'Conversation List', 'A list of threads', new GLib.VariantType('aa{sv}'), null, GObject.ParamFlags.READABLE ), }, }, class SMSPlugin extends Plugin { _init(device) { super._init(device, 'sms'); this.cacheProperties(['_threads']); } get threads() { if (this._threads === undefined) this._threads = {}; return this._threads; } get window() { if (this.settings.get_boolean('legacy-sms')) { return new LegacyMessagingDialog({ device: this.device, plugin: this, }); } if (this._window === undefined) { this._window = new Messaging.Window({ application: Gio.Application.get_default(), device: this.device, plugin: this, }); this._window.connect('destroy', () => { this._window = undefined; }); } return this._window; } clearCache() { this._threads = {}; this.notify('threads'); } cacheLoaded() { this.notify('threads'); } connected() { super.connected(); this._requestConversations(); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.sms.messages': this._handleMessages(packet.body.messages); break; } } /** * Handle a digest of threads. * * @param {Object[]} messages - A list of message objects * @param {string[]} thread_ids - A list of thread IDs as strings */ _handleDigest(messages, thread_ids) { // Prune threads for (const thread_id of Object.keys(this.threads)) { if (!thread_ids.includes(thread_id)) delete this.threads[thread_id]; } // Request each new or newer thread for (let i = 0, len = messages.length; i < len; i++) { const message = messages[i]; const cache = this.threads[message.thread_id]; if (cache === undefined) { this._requestConversation(message.thread_id); continue; } // If this message is marked read, mark the rest as read if (message.read === MessageStatus.READ) { for (const msg of cache) msg.read = MessageStatus.READ; } // If we don't have a thread for this message or it's newer // than the last message in the cache, request the thread if (!cache.length || cache[cache.length - 1].date < message.date) this._requestConversation(message.thread_id); } this.notify('threads'); } /** * Handle a new single message * * @param {Object} message - A message object */ _handleMessage(message) { let conversation = null; // If the window is open, try and find an active conversation if (this._window) conversation = this._window.getConversationForMessage(message); // If there's an active conversation, we should log the message now if (conversation) conversation.logNext(message); } /** * Parse a conversation (thread of messages) and sort them * * @param {Object[]} thread - A list of sms message objects from a thread */ _handleThread(thread) { // If there are no addresses this will cause major problems... if (!thread[0].addresses || !thread[0].addresses[0]) return; const thread_id = thread[0].thread_id; const cache = this.threads[thread_id] || []; // Handle each message for (let i = 0, len = thread.length; i < len; i++) { const message = thread[i]; // TODO: We only cache messages of a known MessageBox since we // have no reliable way to determine its direction, let alone // what to do with it. if (message.type < 0 || message.type > 6) continue; // If the message exists, just update it const cacheMessage = cache.find(m => m.date === message.date); if (cacheMessage) { Object.assign(cacheMessage, message); } else { cache.push(message); this._handleMessage(message); } } // Sort the thread by ascending date and notify this.threads[thread_id] = cache.sort((a, b) => a.date - b.date); this.notify('threads'); } /** * Handle a response to telephony.request_conversation(s) * * @param {Object[]} messages - A list of sms message objects */ _handleMessages(messages) { try { // If messages is empty there's nothing to do... if (messages.length === 0) return; const thread_ids = []; // Perform some modification of the messages for (let i = 0, len = messages.length; i < len; i++) { const message = messages[i]; // COERCION: thread_id's to strings message.thread_id = `${message.thread_id}`; thread_ids.push(message.thread_id); // TODO: Remove bogus `insert-address-token` entries let a = message.addresses.length; while (a--) { if (message.addresses[a].address === undefined || message.addresses[a].address === 'insert-address-token') message.addresses.splice(a, 1); } } // If there's multiple thread_id's it's a summary of threads if (thread_ids.some(id => id !== thread_ids[0])) this._handleDigest(messages, thread_ids); // Otherwise this is single thread or new message else this._handleThread(messages); } catch (e) { debug(e, this.device.name); } } /** * Request a list of messages from a single thread. * * @param {number} thread_id - The id of the thread to request */ _requestConversation(thread_id) { this.device.sendPacket({ type: 'kdeconnect.sms.request_conversation', body: { threadID: thread_id, }, }); } /** * Request a list of the last message in each unarchived thread. */ _requestConversations() { this.device.sendPacket({ type: 'kdeconnect.sms.request_conversations', }); } /** * A notification action for replying to SMS messages (or missed calls). * * @param {string} hint - Could be either a contact name or phone number */ replySms(hint) { this.window.present(); // FIXME: causes problems now that non-numeric addresses are allowed // this.window.address = hint.toPhoneNumber(); } /** * Send an SMS message * * @param {string} phoneNumber - The phone number to send the message to * @param {string} messageBody - The message to send */ sendSms(phoneNumber, messageBody) { this.device.sendPacket({ type: 'kdeconnect.sms.request', body: { sendSms: true, phoneNumber: phoneNumber, messageBody: messageBody, }, }); } /** * Send a message * * @param {Object[]} addresses - A list of address objects * @param {string} messageBody - The message text * @param {number} [event] - An event bitmask * @param {boolean} [forceSms] - Whether to force SMS * @param {number} [subId] - The SIM card to use */ sendMessage(addresses, messageBody, event = 1, forceSms = false, subId = undefined) { // TODO: waiting on support in kdeconnect-android // if (this._version === 1) { this.device.sendPacket({ type: 'kdeconnect.sms.request', body: { sendSms: true, phoneNumber: addresses[0].address, messageBody: messageBody, }, }); // } else if (this._version === 2) { // this.device.sendPacket({ // type: 'kdeconnect.sms.request', // body: { // version: 2, // addresses: addresses, // messageBody: messageBody, // forceSms: forceSms, // sub_id: subId // } // }); // } } /** * Share a text content by SMS message. This is used by the WebExtension to * share URLs from the browser, but could be used to initiate sharing of any * text content. * * @param {string} url - The link to be shared */ shareSms(url) { // Legacy Mode if (this.settings.get_boolean('legacy-sms')) { const window = this.window; window.present(); window.setMessage(url); // If there are active threads, show the chooser dialog } else if (Object.values(this.threads).length > 0) { const window = new Messaging.ConversationChooser({ application: Gio.Application.get_default(), device: this.device, message: url, plugin: this, }); window.present(); // Otherwise show the window and wait for a contact to be chosen } else { this.window.present(); this.window.setMessage(url, true); } } /** * Open and present the messaging window */ sms() { this.window.present(); } /** * This is the sms: URI scheme handler * * @param {string} uri - The URI the handle (sms:|sms://|sms:///) */ uriSms(uri) { try { uri = new SmsURI(uri); // Lookup contacts const addresses = uri.recipients.map(number => { return {address: number.toPhoneNumber()}; }); const contacts = this.device.contacts.lookupAddresses(addresses); // Present the window and show the conversation const window = this.window; window.present(); window.setContacts(contacts); // Set the outgoing message if the uri has a body variable if (uri.body) window.setMessage(uri.body); } catch (e) { debug(e, `${this.device.name}: "${uri}"`); } } _threadHasAddress(thread, addressObj) { const number = addressObj.address.toPhoneNumber(); for (const taddressObj of thread[0].addresses) { const tnumber = taddressObj.address.toPhoneNumber(); if (number.endsWith(tnumber) || tnumber.endsWith(number)) return true; } return false; } /** * Try to find a thread_id in @smsPlugin for @addresses. * * @param {Object[]} addresses - a list of address objects * @return {string|null} a thread ID */ getThreadIdForAddresses(addresses = []) { const threads = Object.values(this.threads); for (const thread of threads) { if (addresses.length !== thread[0].addresses.length) continue; if (addresses.every(addressObj => this._threadHasAddress(thread, addressObj))) return thread[0].thread_id; } return null; } destroy() { if (this._window !== undefined) this._window.destroy(); super.destroy(); } }); export default SMSPlugin; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/systemvolume.js���������������0000664�0000000�0000000�00000013254�14607666711�0031073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GObject from 'gi://GObject'; import * as Components from '../components/index.js'; import Config from '../../config.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('System Volume'), description: _('Enable the paired device to control the system volume'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.SystemVolume', incomingCapabilities: ['kdeconnect.systemvolume.request'], outgoingCapabilities: ['kdeconnect.systemvolume'], actions: {}, }; /** * SystemVolume Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/systemvolume * https://github.com/KDE/kdeconnect-android/tree/master/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/ */ const SystemVolumePlugin = GObject.registerClass({ GTypeName: 'GSConnectSystemVolumePlugin', }, class SystemVolumePlugin extends Plugin { _init(device) { super._init(device, 'systemvolume'); // Cache stream properties this._cache = new WeakMap(); // Connect to the mixer try { this._mixer = Components.acquire('pulseaudio'); this._streamChangedId = this._mixer.connect( 'stream-changed', this._sendSink.bind(this) ); this._outputAddedId = this._mixer.connect( 'output-added', this._sendSinkList.bind(this) ); this._outputRemovedId = this._mixer.connect( 'output-removed', this._sendSinkList.bind(this) ); // Modify the error to redirect to the wiki } catch (e) { e.name = _('PulseAudio not found'); e.url = `${Config.PACKAGE_URL}/wiki/Error#pulseaudio-not-found`; throw e; } } handlePacket(packet) { switch (true) { case packet.body.hasOwnProperty('requestSinks'): this._sendSinkList(); break; case packet.body.hasOwnProperty('name'): this._changeSink(packet); break; } } connected() { super.connected(); this._sendSinkList(); } /** * Handle a request to change an output * * @param {Core.Packet} packet - a `kdeconnect.systemvolume.request` */ _changeSink(packet) { let stream; for (const sink of this._mixer.get_sinks()) { if (sink.name === packet.body.name) { stream = sink; break; } } // No sink with the given name if (stream === undefined) { this._sendSinkList(); return; } // Get a cache and store volume and mute states if changed const cache = this._cache.get(stream) || {}; if (packet.body.hasOwnProperty('muted')) { cache.muted = packet.body.muted; this._cache.set(stream, cache); stream.change_is_muted(packet.body.muted); } if (packet.body.hasOwnProperty('volume')) { cache.volume = packet.body.volume; this._cache.set(stream, cache); stream.volume = packet.body.volume; stream.push_volume(); } } /** * Update the cache for @stream * * @param {Gvc.MixerStream} stream - The stream to cache * @return {Object} The updated cache object */ _updateCache(stream) { const state = { name: stream.name, description: stream.display_name, muted: stream.is_muted, volume: stream.volume, maxVolume: this._mixer.get_vol_max_norm(), }; this._cache.set(stream, state); return state; } /** * Send the state of a local sink * * @param {Gvc.MixerControl} mixer - The mixer that owns the stream * @param {number} id - The Id of the stream that changed */ _sendSink(mixer, id) { // Avoid starving the packet channel when fading if (this._mixer.fading) return; // Check the cache const stream = this._mixer.lookup_stream_id(id); const cache = this._cache.get(stream) || {}; // If the port has changed we have to send the whole list to update the // display name if (!cache.display_name || cache.display_name !== stream.display_name) { this._sendSinkList(); return; } // If only volume and/or mute are set, send a single update if (cache.volume !== stream.volume || cache.muted !== stream.is_muted) { // Update the cache const state = this._updateCache(stream); // Send the stream update this.device.sendPacket({ type: 'kdeconnect.systemvolume', body: state, }); } } /** * Send a list of local sinks */ _sendSinkList() { const sinkList = this._mixer.get_sinks().map(sink => { return this._updateCache(sink); }); // Send the sinkList this.device.sendPacket({ type: 'kdeconnect.systemvolume', body: { sinkList: sinkList, }, }); } destroy() { if (this._mixer !== undefined) { this._mixer.disconnect(this._streamChangedId); this._mixer.disconnect(this._outputAddedId); this._mixer.disconnect(this._outputRemovedId); this._mixer = Components.release('pulseaudio'); } super.destroy(); } }); export default SystemVolumePlugin; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/plugins/telephony.js������������������0000664�0000000�0000000�00000016062�14607666711�0030326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GdkPixbuf from 'gi://GdkPixbuf'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import * as Components from '../components/index.js'; import Plugin from '../plugin.js'; export const Metadata = { label: _('Telephony'), description: _('Be notified about calls and adjust system volume during ringing/ongoing calls'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Telephony', incomingCapabilities: [ 'kdeconnect.telephony', ], outgoingCapabilities: [ 'kdeconnect.telephony.request', 'kdeconnect.telephony.request_mute', ], actions: { muteCall: { // TRANSLATORS: Silence the actively ringing call label: _('Mute Call'), icon_name: 'audio-volume-muted-symbolic', parameter_type: null, incoming: ['kdeconnect.telephony'], outgoing: ['kdeconnect.telephony.request_mute'], }, }, }; /** * Telephony Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/telephony * https://github.com/KDE/kdeconnect-android/tree/master/src/org/kde/kdeconnect/Plugins/TelephonyPlugin */ const TelephonyPlugin = GObject.registerClass({ GTypeName: 'GSConnectTelephonyPlugin', }, class TelephonyPlugin extends Plugin { _init(device) { super._init(device, 'telephony'); // Neither of these are crucial for the plugin to work this._mpris = Components.acquire('mpris'); this._mixer = Components.acquire('pulseaudio'); } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.telephony': this._handleEvent(packet); break; } } /** * Change volume, microphone and media player state in response to an * incoming or answered call. * * @param {string} eventType - 'ringing' or 'talking' */ _setMediaState(eventType) { // Mixer Volume if (this._mixer !== undefined) { switch (this.settings.get_string(`${eventType}-volume`)) { case 'restore': this._mixer.restore(); break; case 'lower': this._mixer.lowerVolume(); break; case 'mute': this._mixer.muteVolume(); break; } if (eventType === 'talking' && this.settings.get_boolean('talking-microphone')) this._mixer.muteMicrophone(); } // Media Playback if (this._mpris && this.settings.get_boolean(`${eventType}-pause`)) this._mpris.pauseAll(); } /** * Restore volume, microphone and media player state (if changed), making * sure to unpause before raising volume. * * TODO: there's a possibility we might revert a media/mixer state set for * another device. */ _restoreMediaState() { // Media Playback if (this._mpris) this._mpris.unpauseAll(); // Mixer Volume if (this._mixer) this._mixer.restore(); } /** * Load a Gdk.Pixbuf from base64 encoded data * * @param {string} data - Base64 encoded JPEG data * @return {Gdk.Pixbuf|null} A contact photo */ _getThumbnailPixbuf(data) { const loader = new GdkPixbuf.PixbufLoader(); try { data = GLib.base64_decode(data); loader.write(data); loader.close(); } catch (e) { debug(e, this.device.name); } return loader.get_pixbuf(); } /** * Handle a telephony event (ringing, talking), showing or hiding a * notification and possibly adjusting the media/mixer state. * * @param {Core.Packet} packet - A `kdeconnect.telephony` */ _handleEvent(packet) { // Only handle 'ringing' or 'talking' events; leave the notification // plugin to handle 'missedCall' since they're often repliable if (!['ringing', 'talking'].includes(packet.body.event)) return; // This is the end of a telephony event if (packet.body.isCancel) this._cancelEvent(packet); else this._notifyEvent(packet); } _cancelEvent(packet) { // Ensure we have a sender // TRANSLATORS: No name or phone number let sender = _('Unknown Contact'); if (packet.body.contactName) sender = packet.body.contactName; else if (packet.body.phoneNumber) sender = packet.body.phoneNumber; this.device.hideNotification(`${packet.body.event}|${sender}`); this._restoreMediaState(); } _notifyEvent(packet) { let body; let buttons = []; let icon = null; let priority = Gio.NotificationPriority.NORMAL; // Ensure we have a sender // TRANSLATORS: No name or phone number let sender = _('Unknown Contact'); if (packet.body.contactName) sender = packet.body.contactName; else if (packet.body.phoneNumber) sender = packet.body.phoneNumber; // If there's a photo, use it as the notification icon if (packet.body.phoneThumbnail) icon = this._getThumbnailPixbuf(packet.body.phoneThumbnail); if (icon === null) icon = new Gio.ThemedIcon({name: 'call-start-symbolic'}); // Notify based based on the event type if (packet.body.event === 'ringing') { this._setMediaState('ringing'); // TRANSLATORS: The phone is ringing body = _('Incoming call'); buttons = [{ action: 'muteCall', // TRANSLATORS: Silence the actively ringing call label: _('Mute'), parameter: null, }]; priority = Gio.NotificationPriority.URGENT; } if (packet.body.event === 'talking') { this.device.hideNotification(`ringing|${sender}`); this._setMediaState('talking'); // TRANSLATORS: A phone call is active body = _('Ongoing call'); } this.device.showNotification({ id: `${packet.body.event}|${sender}`, title: sender, body: body, icon: icon, priority: priority, buttons: buttons, }); } /** * Silence an incoming call and restore the previous mixer/media state, if * applicable. */ muteCall() { this.device.sendPacket({ type: 'kdeconnect.telephony.request_mute', body: {}, }); this._restoreMediaState(); } destroy() { if (this._mixer !== undefined) this._mixer = Components.release('pulseaudio'); if (this._mpris !== undefined) this._mpris = Components.release('mpris'); super.destroy(); } }); export default TelephonyPlugin; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/ui/�����������������������������������0000775�0000000�0000000�00000000000�14607666711�0024710�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/ui/contacts.js������������������������0000664�0000000�0000000�00000043144�14607666711�0027072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import GdkPixbuf from 'gi://GdkPixbuf'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import system from 'system'; /** * Return a random color * * @param {*} [salt] - If not %null, will be used as salt for generating a color * @param {number} alpha - A value in the [0...1] range for the alpha channel * @return {Gdk.RGBA} A new Gdk.RGBA object generated from the input */ function randomRGBA(salt = null, alpha = 1.0) { let red, green, blue; if (salt !== null) { const hash = new GLib.Variant('s', `${salt}`).hash(); red = ((hash & 0xFF0000) >> 16) / 255; green = ((hash & 0x00FF00) >> 8) / 255; blue = (hash & 0x0000FF) / 255; } else { red = Math.random(); green = Math.random(); blue = Math.random(); } return new Gdk.RGBA({red: red, green: green, blue: blue, alpha: alpha}); } /** * Get the relative luminance of a RGB set * See: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef * * @param {Gdk.RGBA} rgba - A GdkRGBA object * @return {number} The relative luminance of the color */ function relativeLuminance(rgba) { const {red, green, blue} = rgba; const R = (red > 0.03928) ? red / 12.92 : Math.pow(((red + 0.055) / 1.055), 2.4); const G = (green > 0.03928) ? green / 12.92 : Math.pow(((green + 0.055) / 1.055), 2.4); const B = (blue > 0.03928) ? blue / 12.92 : Math.pow(((blue + 0.055) / 1.055), 2.4); return 0.2126 * R + 0.7152 * G + 0.0722 * B; } /** * Get a GdkRGBA contrasted for the input * See: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef * * @param {Gdk.RGBA} rgba - A GdkRGBA object for the background color * @return {Gdk.RGBA} A GdkRGBA object for the foreground color */ function getFgRGBA(rgba) { const bgLuminance = relativeLuminance(rgba); const lightContrast = (0.07275541795665634 + 0.05) / (bgLuminance + 0.05); const darkContrast = (bgLuminance + 0.05) / (0.0046439628482972135 + 0.05); const value = (darkContrast > lightContrast) ? 0.06 : 0.94; return new Gdk.RGBA({red: value, green: value, blue: value, alpha: 0.5}); } /** * Get a GdkPixbuf for @path, allowing the corrupt JPEG's KDE Connect sometimes * sends. This function is synchronous. * * @param {string} path - A local file path * @param {number} size - Size in pixels * @param {scale} [scale] - Scale factor for the size * @return {Gdk.Pixbuf} A pixbuf */ function getPixbufForPath(path, size, scale = 1.0) { let data, loader; // Catch missing avatar files try { data = GLib.file_get_contents(path)[1]; } catch (e) { debug(e, path); return undefined; } // Consider errors from partially corrupt JPEGs to be warnings try { loader = new GdkPixbuf.PixbufLoader(); loader.write(data); loader.close(); } catch (e) { debug(e, path); } const pixbuf = loader.get_pixbuf(); // Scale to monitor size = Math.floor(size * scale); return pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.HYPER); } function getPixbufForIcon(name, size, scale, bgColor) { const color = getFgRGBA(bgColor); const theme = Gtk.IconTheme.get_default(); const info = theme.lookup_icon_for_scale( name, size, scale, Gtk.IconLookupFlags.FORCE_SYMBOLIC ); return info.load_symbolic(color, null, null, null)[0]; } /** * Return a localized string for a phone number type * See: http://www.ietf.org/rfc/rfc2426.txt * * @param {string} type - An RFC2426 phone number type * @return {string} A localized string like 'Mobile' */ function getNumberTypeLabel(type) { if (type.includes('fax')) // TRANSLATORS: A fax number return _('Fax'); if (type.includes('work')) // TRANSLATORS: A work or office phone number return _('Work'); if (type.includes('cell')) // TRANSLATORS: A mobile or cellular phone number return _('Mobile'); if (type.includes('home')) // TRANSLATORS: A home phone number return _('Home'); // TRANSLATORS: All other phone number types return _('Other'); } /** * Get a display number from @contact for @address. * * @param {Object} contact - A contact object * @param {string} address - A phone number * @return {string} A (possibly) better display number for the address */ export function getDisplayNumber(contact, address) { const number = address.toPhoneNumber(); for (const contactNumber of contact.numbers) { const cnumber = contactNumber.value.toPhoneNumber(); if (number.endsWith(cnumber) || cnumber.endsWith(number)) return GLib.markup_escape_text(contactNumber.value, -1); } return GLib.markup_escape_text(address, -1); } /** * Contact Avatar */ const AvatarCache = new WeakMap(); export const Avatar = GObject.registerClass({ GTypeName: 'GSConnectContactAvatar', }, class ContactAvatar extends Gtk.DrawingArea { _init(contact = null) { super._init({ height_request: 32, width_request: 32, valign: Gtk.Align.CENTER, visible: true, }); this.contact = contact; } get rgba() { if (this._rgba === undefined) { if (this.contact) this._rgba = randomRGBA(this.contact.name); else this._rgba = randomRGBA(GLib.uuid_string_random()); } return this._rgba; } get contact() { if (this._contact === undefined) this._contact = null; return this._contact; } set contact(contact) { if (this.contact === contact) return; this._contact = contact; this._surface = undefined; this._rgba = undefined; this._offset = 0; } _loadSurface() { // Get the monitor scale const display = Gdk.Display.get_default(); const monitor = display.get_monitor_at_window(this.get_window()); const scale = monitor.get_scale_factor(); // If there's a contact with an avatar, try to load it if (this.contact && this.contact.avatar) { // Check the cache this._surface = AvatarCache.get(this.contact); // Try loading the pixbuf if (!this._surface) { const pixbuf = getPixbufForPath( this.contact.avatar, this.width_request, scale ); if (pixbuf) { this._surface = Gdk.cairo_surface_create_from_pixbuf( pixbuf, 0, this.get_window() ); AvatarCache.set(this.contact, this._surface); } } } // If we still don't have a surface, load a fallback if (!this._surface) { let iconName; // If we were given a contact, it's direct message otherwise group if (this.contact) iconName = 'avatar-default-symbolic'; else iconName = 'group-avatar-symbolic'; // Center the icon this._offset = (this.width_request - 24) / 2; // Load the fallback const pixbuf = getPixbufForIcon(iconName, 24, scale, this.rgba); this._surface = Gdk.cairo_surface_create_from_pixbuf( pixbuf, 0, this.get_window() ); } } vfunc_draw(cr) { if (!this._surface) this._loadSurface(); // Clip to a circle const rad = this.width_request / 2; cr.arc(rad, rad, rad, 0, 2 * Math.PI); cr.clipPreserve(); // Fill the background if the the surface is offset if (this._offset > 0) { Gdk.cairo_set_source_rgba(cr, this.rgba); cr.fill(); } // Draw the avatar/icon cr.setSourceSurface(this._surface, this._offset, this._offset); cr.paint(); cr.$dispose(); return Gdk.EVENT_PROPAGATE; } }); /** * A row for a contact address (usually a phone number). */ const AddressRow = GObject.registerClass({ GTypeName: 'GSConnectContactsAddressRow', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/contacts-address-row.ui', Children: ['avatar', 'name-label', 'address-label', 'type-label'], }, class AddressRow extends Gtk.ListBoxRow { _init(contact, index = 0) { super._init(); this._index = index; this._number = contact.numbers[index]; this.contact = contact; } get contact() { if (this._contact === undefined) this._contact = null; return this._contact; } set contact(contact) { if (this.contact === contact) return; this._contact = contact; if (this._index === 0) { this.avatar.contact = contact; this.avatar.visible = true; this.name_label.label = GLib.markup_escape_text(contact.name, -1); this.name_label.visible = true; this.address_label.margin_start = 0; this.address_label.margin_end = 0; } else { this.avatar.visible = false; this.name_label.visible = false; // TODO: rtl inverts margin-start so the number don't align this.address_label.margin_start = 38; this.address_label.margin_end = 38; } this.address_label.label = GLib.markup_escape_text(this.number.value, -1); if (this.number.type !== undefined) this.type_label.label = getNumberTypeLabel(this.number.type); } get number() { if (this._number === undefined) return {value: 'unknown', type: 'unknown'}; return this._number; } }); /** * A widget for selecting contact addresses (usually phone numbers) */ export const ContactChooser = GObject.registerClass({ GTypeName: 'GSConnectContactChooser', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE, GObject.Object ), 'store': GObject.ParamSpec.object( 'store', 'Store', 'The contacts store', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, GObject.Object ), }, Signals: { 'number-selected': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [GObject.TYPE_STRING], }, }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/contact-chooser.ui', Children: ['entry', 'list', 'scrolled'], }, class ContactChooser extends Gtk.Grid { _init(params) { super._init(params); // Setup the contact list this.list._entry = this.entry.text; this.list.set_filter_func(this._filter); this.list.set_sort_func(this._sort); // Make sure we're using the correct contacts store this.device.bind_property( 'contacts', this, 'store', GObject.BindingFlags.SYNC_CREATE ); // Cleanup on ::destroy this.connect('destroy', this._onDestroy); } get store() { if (this._store === undefined) this._store = null; return this._store; } set store(store) { if (this.store === store) return; // Unbind the old store if (this._store) { // Disconnect from the store this._store.disconnect(this._contactAddedId); this._store.disconnect(this._contactRemovedId); this._store.disconnect(this._contactChangedId); // Clear the contact list const rows = this.list.get_children(); for (let i = 0, len = rows.length; i < len; i++) { rows[i].destroy(); // HACK: temporary mitigator for mysterious GtkListBox leak system.gc(); } } // Set the store this._store = store; // Bind the new store if (this._store) { // Connect to the new store this._contactAddedId = store.connect( 'contact-added', this._onContactAdded.bind(this) ); this._contactRemovedId = store.connect( 'contact-removed', this._onContactRemoved.bind(this) ); this._contactChangedId = store.connect( 'contact-changed', this._onContactChanged.bind(this) ); // Populate the list this._populate(); } } /* * ContactStore Callbacks */ _onContactAdded(store, id) { const contact = this.store.get_contact(id); this._addContact(contact); } _onContactRemoved(store, id) { const rows = this.list.get_children(); for (let i = 0, len = rows.length; i < len; i++) { const row = rows[i]; if (row.contact.id === id) { row.destroy(); // HACK: temporary mitigator for mysterious GtkListBox leak system.gc(); } } } _onContactChanged(store, id) { this._onContactRemoved(store, id); this._onContactAdded(store, id); } _onDestroy(chooser) { chooser.store = null; } _onSearchChanged(entry) { this.list._entry = entry.text; let dynamic = this.list.get_row_at_index(0); // If the entry contains string with 2 or more digits... if (entry.text.replace(/\D/g, '').length >= 2) { // ...ensure we have a dynamic contact for it if (!dynamic || !dynamic.__tmp) { dynamic = new AddressRow({ // TRANSLATORS: A phone number (eg. "Send to 555-5555") name: _('Send to %s').format(entry.text), numbers: [{type: 'unknown', value: entry.text}], }); dynamic.__tmp = true; this.list.add(dynamic); // ...or if we already do, then update it } else { const address = entry.text; // Update contact object dynamic.contact.name = address; dynamic.contact.numbers[0].value = address; // Update UI dynamic.name_label.label = _('Send to %s').format(address); dynamic.address_label.label = address; } // ...otherwise remove any dynamic contact that's been created } else if (dynamic && dynamic.__tmp) { dynamic.destroy(); } this.list.invalidate_filter(); this.list.invalidate_sort(); } // GtkListBox::row-activated _onNumberSelected(box, row) { if (row === null) return; // Emit the number const address = row.number.value; this.emit('number-selected', address); // Reset the contact list this.entry.text = ''; this.list.select_row(null); this.scrolled.vadjustment.value = 0; } _filter(row) { // Dynamic contact always shown if (row.__tmp) return true; const query = row.get_parent()._entry; // Show contact if text is substring of name const queryName = query.toLocaleLowerCase(); if (row.contact.name.toLocaleLowerCase().includes(queryName)) return true; // Show contact if text is substring of number const queryNumber = query.toPhoneNumber(); if (queryNumber.length) { for (const number of row.contact.numbers) { if (number.value.toPhoneNumber().includes(queryNumber)) return true; } // Query is effectively empty } else if (/^0+/.test(query)) { return true; } return false; } _sort(row1, row2) { if (row1.__tmp) return -1; if (row2.__tmp) return 1; return row1.contact.name.localeCompare(row2.contact.name); } _populate() { // Add each contact const contacts = this.store.contacts; for (let i = 0, len = contacts.length; i < len; i++) this._addContact(contacts[i]); } _addContactNumber(contact, index) { const row = new AddressRow(contact, index); this.list.add(row); return row; } _addContact(contact) { try { // HACK: fix missing contact names if (contact.name === undefined) contact.name = _('Unknown Contact'); if (contact.numbers.length === 1) return this._addContactNumber(contact, 0); for (let i = 0, len = contact.numbers.length; i < len; i++) this._addContactNumber(contact, i); } catch (e) { logError(e); } } /** * Get a dictionary of number-contact pairs for each selected phone number. * * @return {Object[]} A dictionary of contacts */ getSelected() { try { const selected = {}; for (const row of this.list.get_selected_rows()) selected[row.number.value] = row.contact; return selected; } catch (e) { logError(e); return {}; } } }); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/ui/legacyMessaging.js�����������������0000664�0000000�0000000�00000014166�14607666711�0030360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import * as Contacts from '../ui/contacts.js'; import * as Messaging from '../ui/messaging.js'; import * as URI from '../utils/uri.js'; import '../utils/ui.js'; const Dialog = GObject.registerClass({ GTypeName: 'GSConnectLegacyMessagingDialog', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE, GObject.Object ), 'plugin': GObject.ParamSpec.object( 'plugin', 'Plugin', 'The plugin providing messages', GObject.ParamFlags.READWRITE, GObject.Object ), }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/legacy-messaging-dialog.ui', Children: [ 'infobar', 'stack', 'message-box', 'message-avatar', 'message-label', 'entry', ], }, class Dialog extends Gtk.Dialog { _init(params) { super._init({ application: Gio.Application.get_default(), device: params.device, plugin: params.plugin, use_header_bar: true, }); this.set_response_sensitive(Gtk.ResponseType.OK, false); // Dup some functions this.headerbar = this.get_titlebar(); this._setHeaderBar = Messaging.Window.prototype._setHeaderBar; // Info bar this.device.bind_property( 'connected', this.infobar, 'reveal-child', GObject.BindingFlags.INVERT_BOOLEAN ); // Message Entry/Send Button this.device.bind_property( 'connected', this.entry, 'sensitive', GObject.BindingFlags.DEFAULT ); this._connectedId = this.device.connect( 'notify::connected', this._onStateChanged.bind(this) ); this._entryChangedId = this.entry.buffer.connect( 'changed', this._onStateChanged.bind(this) ); // Set the message if given if (params.message) { this.message = params.message; this.addresses = params.message.addresses; this.message_avatar.contact = this.device.contacts.query({ number: this.addresses[0].address, }); this.message_label.label = URI.linkify(this.message.body); this.message_box.visible = true; // Otherwise set the address(es) if we were passed those } else if (params.addresses) { this.addresses = params.addresses; } // Load the contact list if we weren't supplied with an address if (this.addresses.length === 0) { this.contact_chooser = new Contacts.ContactChooser({ device: this.device, }); this.stack.add_named(this.contact_chooser, 'contact-chooser'); this.stack.child_set_property(this.contact_chooser, 'position', 0); this._numberSelectedId = this.contact_chooser.connect( 'number-selected', this._onNumberSelected.bind(this) ); this.stack.visible_child_name = 'contact-chooser'; } this.restoreGeometry('legacy-messaging-dialog'); this.connect('destroy', this._onDestroy); } _onDestroy(dialog) { if (dialog._numberSelectedId !== undefined) { dialog.contact_chooser.disconnect(dialog._numberSelectedId); dialog.contact_chooser.destroy(); } dialog.entry.buffer.disconnect(dialog._entryChangedId); dialog.device.disconnect(dialog._connectedId); } vfunc_delete_event() { this.saveGeometry(); return false; } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { // Refuse to send empty or whitespace only texts if (!this.entry.buffer.text.trim()) return; this.plugin.sendMessage( this.addresses, this.entry.buffer.text, 1, true ); } this.destroy(); } get addresses() { if (this._addresses === undefined) this._addresses = []; return this._addresses; } set addresses(addresses = []) { this._addresses = addresses; // Set the headerbar this._setHeaderBar(this._addresses); // Show the message editor this.stack.visible_child_name = 'message-editor'; this._onStateChanged(); } get device() { if (this._device === undefined) this._device = null; return this._device; } set device(device) { this._device = device; } get plugin() { if (this._plugin === undefined) this._plugin = null; return this._plugin; } set plugin(plugin) { this._plugin = plugin; } _onActivateLink(label, uri) { Gtk.show_uri_on_window( this.get_toplevel(), uri.includes('://') ? uri : `https://${uri}`, Gtk.get_current_event_time() ); return true; } _onNumberSelected(chooser, number) { const contacts = chooser.getSelected(); this.addresses = Object.keys(contacts).map(address => { return {address: address}; }); } _onStateChanged() { if (this.device.connected && this.entry.buffer.text.trim() && this.stack.visible_child_name === 'message-editor') this.set_response_sensitive(Gtk.ResponseType.OK, true); else this.set_response_sensitive(Gtk.ResponseType.OK, false); } /** * Set the contents of the message entry * * @param {string} text - The message to place in the entry */ setMessage(text) { this.entry.buffer.text = text; } }); export default Dialog; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/ui/messaging.js�����������������������0000664�0000000�0000000�00000113301�14607666711�0027222�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import Pango from 'gi://Pango'; import system from 'system'; import * as Contacts from './contacts.js'; import * as Sms from '../plugins/sms.js'; import * as URI from '../utils/uri.js'; import '../utils/ui.js'; const Tweener = imports.tweener.tweener; /* * Useful time constants */ const TIME_SPAN_MINUTE = 60000; const TIME_SPAN_HOUR = 3600000; const TIME_SPAN_DAY = 86400000; const TIME_SPAN_WEEK = 604800000; // Less than an hour (eg. 42 minutes ago) const _lthLong = new Intl.RelativeTimeFormat('default', { numeric: 'auto', style: 'long', }); // Less than a day ago (eg. 11:42 PM) const _ltdFormat = new Intl.DateTimeFormat('default', { hour: 'numeric', minute: 'numeric', }); // Less than a week ago (eg. Monday) const _ltwLong = new Intl.DateTimeFormat('default', { weekday: 'long', }); // Less than a week ago (eg. Mon) const _ltwShort = new Intl.DateTimeFormat('default', { weekday: 'short', }); // Less than a year (eg. Oct 31) const _ltyShort = new Intl.DateTimeFormat('default', { day: 'numeric', month: 'short', }); // Less than a year (eg. October 31) const _ltyLong = new Intl.DateTimeFormat('default', { day: 'numeric', month: 'long', }); // Greater than a year (eg. October 31, 2019) const _gtyLong = new Intl.DateTimeFormat('default', { day: 'numeric', month: 'long', year: 'numeric', }); // Greater than a year (eg. 10/31/2019) const _gtyShort = new Intl.DateTimeFormat('default', { day: 'numeric', month: 'numeric', year: 'numeric', }); // Pretty close to strftime's %c const _cFormat = new Intl.DateTimeFormat('default', { year: 'numeric', month: 'short', day: 'numeric', weekday: 'short', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short', }); /** * Return a human-readable timestamp, formatted for longer contexts. * * @param {number} time - Milliseconds since the epoch (local time) * @return {string} A localized timestamp similar to what Android Messages uses */ function getTime(time) { const date = new Date(time); const now = new Date(); const diff = now - time; // Super recent if (diff < TIME_SPAN_MINUTE) // TRANSLATORS: Less than a minute ago return _('Just now'); // Under an hour (TODO: these labels aren't updated) if (diff < TIME_SPAN_HOUR) return _lthLong.format(-Math.floor(diff / TIME_SPAN_MINUTE), 'minute'); // Yesterday, but less than 24 hours ago if (diff < TIME_SPAN_DAY && now.getDay() !== date.getDay()) // TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) return _('Yesterday・%s').format(_ltdFormat.format(time)); // Less than a day ago if (diff < TIME_SPAN_DAY) return _ltdFormat.format(time); // Less than a week ago if (diff < TIME_SPAN_WEEK) return _ltwLong.format(time); // Sometime this year if (date.getFullYear() === now.getFullYear()) return _ltyLong.format(time); // Earlier than that return _gtyLong.format(time); } /** * Return a human-readable timestamp, formatted for shorter contexts. * * @param {number} time - Milliseconds since the epoch (local time) * @return {string} A localized timestamp similar to what Android Messages uses */ function getShortTime(time) { const date = new Date(time); const now = new Date(); const diff = now - time; if (diff < TIME_SPAN_MINUTE) // TRANSLATORS: Less than a minute ago return _('Just now'); if (diff < TIME_SPAN_HOUR) { // TRANSLATORS: Time duration in minutes (eg. 15 minutes) return ngettext( '%d minute', '%d minutes', (diff / TIME_SPAN_MINUTE) ).format(diff / TIME_SPAN_MINUTE); } // Less than a day ago if (diff < TIME_SPAN_DAY) return _ltdFormat.format(time); // Less than a week ago if (diff < TIME_SPAN_WEEK) return _ltwShort.format(time); // Sometime this year if (date.getFullYear() === now.getFullYear()) return _ltyShort.format(time); // Earlier than that return _gtyShort.format(time); } /** * Return a human-readable timestamp, similar to `strftime()` with `%c`. * * @param {number} time - Milliseconds since the epoch (local time) * @return {string} A localized timestamp */ function getDetailedTime(time) { return _cFormat.format(time); } function setAvatarVisible(row, visible) { const incoming = row.message.type === Sms.MessageBox.INBOX; // Adjust the margins if (visible) { row.grid.margin_start = incoming ? 6 : 56; row.grid.margin_bottom = 6; } else { row.grid.margin_start = incoming ? 44 : 56; row.grid.margin_bottom = 0; } // Show hide the avatar if (incoming) row.avatar.visible = visible; } /** * A ListBoxRow for a preview of a conversation */ const ConversationMessage = GObject.registerClass({ GTypeName: 'GSConnectMessagingConversationMessage', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/messaging-conversation-message.ui', Children: ['grid', 'avatar', 'sender-label', 'message-label'], }, class ConversationMessage extends Gtk.ListBoxRow { _init(contact, message) { super._init(); this.contact = contact; this.message = message; // Sort properties this.sender = message.addresses[0].address || 'unknown'; this.message_label.label = URI.linkify(message.body); this.message_label.tooltip_text = getDetailedTime(message.date); // Add avatar for incoming messages if (message.type === Sms.MessageBox.INBOX) { this.grid.margin_end = 18; this.grid.halign = Gtk.Align.START; this.avatar.contact = this.contact; this.avatar.visible = true; this.sender_label.label = contact.name; this.sender_label.visible = true; this.message_label.get_style_context().add_class('message-in'); this.message_label.halign = Gtk.Align.START; } else { this.message_label.get_style_context().add_class('message-out'); } } _onActivateLink(label, uri) { Gtk.show_uri_on_window( this.get_toplevel(), uri.includes('://') ? uri : `https://${uri}`, Gtk.get_current_event_time() ); return true; } get date() { return this._message.date; } get thread_id() { return this._message.thread_id; } get message() { if (this._message === undefined) this._message = null; return this._message; } set message(message) { this._message = message; } }); /** * A widget for displaying a conversation thread, with an entry for responding. */ const Conversation = GObject.registerClass({ GTypeName: 'GSConnectMessagingConversation', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this conversation', GObject.ParamFlags.READWRITE, GObject.Object ), 'plugin': GObject.ParamSpec.object( 'plugin', 'Plugin', 'The plugin providing this conversation', GObject.ParamFlags.READWRITE, GObject.Object ), 'has-pending': GObject.ParamSpec.boolean( 'has-pending', 'Has Pending', 'Whether there are sent messages pending confirmation', GObject.ParamFlags.READABLE, false ), 'thread-id': GObject.ParamSpec.string( 'thread-id', 'Thread ID', 'The current thread', GObject.ParamFlags.READWRITE, '' ), }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/messaging-conversation.ui', Children: [ 'entry', 'list', 'scrolled', 'pending', 'pending-box', ], }, class MessagingConversation extends Gtk.Grid { _init(params) { super._init({ device: params.device, plugin: params.plugin, }); Object.assign(this, params); this.device.bind_property( 'connected', this.entry, 'sensitive', GObject.BindingFlags.SYNC_CREATE ); // If we're disconnected pending messages might not succeed, but we'll // leave them until reconnect when we'll ask for an update this._connectedId = this.device.connect( 'notify::connected', this._onConnected.bind(this) ); // Pending messages this.pending.message = { date: Number.MAX_SAFE_INTEGER, type: Sms.MessageBox.OUTBOX, }; // Auto-scrolling this._vadj = this.scrolled.get_vadjustment(); this._scrolledId = this._vadj.connect( 'value-changed', this._holdPosition.bind(this) ); // Message List this.list.set_header_func(this._headerMessages); this.list.set_sort_func(this._sortMessages); this._populateMessages(); // Cleanup on ::destroy this.connect('destroy', this._onDestroy); } get addresses() { if (this._addresses === undefined) this._addresses = []; return this._addresses; } set addresses(addresses) { if (!addresses || addresses.length === 0) { this._addresses = []; this._contacts = {}; return; } // Lookup a contact for each address object, then loop back to correct // each address carried by the message. this._addresses = addresses; for (let i = 0, len = this.addresses.length; i < len; i++) { // Lookup the contact const address = this.addresses[i].address; const contact = this.device.contacts.query({number: address}); // Get corrected address let number = address.toPhoneNumber(); if (!number) continue; for (const contactNumber of contact.numbers) { const cnumber = contactNumber.value.toPhoneNumber(); if (cnumber && (number.endsWith(cnumber) || cnumber.endsWith(number))) { number = contactNumber.value; break; } } // Store the final result this.addresses[i].address = number; this.contacts[address] = contact; } // TODO: Mark the entry as insensitive for group messages if (this.addresses.length > 1) { this.entry.placeholder_text = _('Not available'); this.entry.secondary_icon_name = null; this.entry.secondary_icon_tooltip_text = null; this.entry.sensitive = false; this.entry.tooltip_text = null; } } get contacts() { if (this._contacts === undefined) this._contacts = {}; return this._contacts; } get has_pending() { if (this.pending_box === undefined) return false; return (this.pending_box.get_children().length > 0); } get plugin() { if (this._plugin === undefined) this._plugin = null; return this._plugin; } set plugin(plugin) { this._plugin = plugin; } get thread_id() { if (this._thread_id === undefined) this._thread_id = null; return this._thread_id; } set thread_id(thread_id) { const thread = this.plugin.threads[thread_id]; const message = (thread) ? thread[0] : null; if (message && this.addresses.length === 0) { this.addresses = message.addresses; this._thread_id = thread_id; } } _onConnected(device) { if (device.connected) this.pending_box.foreach(msg => msg.destroy()); } _onDestroy(conversation) { conversation.device.disconnect(conversation._connectedId); conversation._vadj.disconnect(conversation._scrolledId); conversation.list.foreach(message => { // HACK: temporary mitigator for mysterious GtkListBox leak message.destroy(); system.gc(); }); } _onEdgeReached(scrolled_window, pos) { // Try to load more messages if (pos === Gtk.PositionType.TOP) this.logPrevious(); // Release any hold to resume auto-scrolling else if (pos === Gtk.PositionType.BOTTOM) this._releasePosition(); } _onEntryChanged(entry) { entry.secondary_icon_sensitive = (entry.text.length); } _onKeyPressEvent(entry, event) { const keyval = event.get_keyval()[1]; const state = event.get_state()[1]; const mask = state & Gtk.accelerator_get_default_mod_mask(); if (keyval === Gdk.KEY_Return && (mask & Gdk.ModifierType.SHIFT_MASK)) { entry.emit('insert-at-cursor', '\n'); return true; } return false; } _onSendMessage(entry, signal_id, event) { // Don't send empty texts if (!this.entry.text.trim()) return; // Send the message this.plugin.sendMessage(this.addresses, this.entry.text); // Add a phony message in the pending box const message = new Gtk.Label({ label: URI.linkify(this.entry.text), halign: Gtk.Align.END, selectable: true, use_markup: true, visible: true, wrap: true, wrap_mode: Pango.WrapMode.WORD_CHAR, xalign: 0, }); message.get_style_context().add_class('message-out'); message.date = Date.now(); message.type = Sms.MessageBox.SENT; // Notify to reveal the pending box this.pending_box.add(message); this.notify('has-pending'); // Clear the entry this.entry.text = ''; } _onSizeAllocate(listbox, allocation) { const upper = this._vadj.get_upper(); const pageSize = this._vadj.get_page_size(); // If the scrolled window hasn't been filled yet, load another message if (upper <= pageSize) { this.logPrevious(); this.scrolled.get_child().check_resize(); // We've been asked to hold the position, so we'll reset the adjustment // value and update the hold position } else if (this.__pos) { this._vadj.set_value(upper - this.__pos); // Otherwise we probably appended a message and should scroll to it } else { this._scrollPosition(Gtk.PositionType.BOTTOM); } } /** * Create a message row, ensuring a contact object has been retrieved or * generated for the message. * * @param {Object} message - A dictionary of message data * @return {ConversationMessage} A message row */ _createMessageRow(message) { // Ensure we have a contact const sender = message.addresses[0].address || 'unknown'; if (this.contacts[sender] === undefined) { this.contacts[sender] = this.device.contacts.query({ number: sender, }); } return new ConversationMessage(this.contacts[sender], message); } _populateMessages() { this.__first = null; this.__last = null; this.__pos = 0; this.__messages = []; // Try and find a thread_id for this number if (this.thread_id === null && this.addresses.length) this._thread_id = this.plugin.getThreadIdForAddresses(this.addresses); // Make a copy of the thread and fill the window with messages if (this.plugin.threads[this.thread_id]) { this.__messages = this.plugin.threads[this.thread_id].slice(0); this.logPrevious(); } } _headerMessages(row, before) { // Skip pending if (row.get_name() === 'pending') return; if (before === null) return setAvatarVisible(row, true); // Add date header if the last message was more than an hour ago let header = row.get_header(); if ((row.message.date - before.message.date) > TIME_SPAN_HOUR) { if (!header) { header = new Gtk.Label({visible: true, selectable: true}); header.get_style_context().add_class('dim-label'); row.set_header(header); } header.label = getTime(row.message.date); // Also show the avatar setAvatarVisible(row, true); row.sender_label.visible = row.message.addresses.length > 1; // Or if the previous sender was the same, hide its avatar } else if (row.message.type === before.message.type && row.sender.equalsPhoneNumber(before.sender)) { setAvatarVisible(before, false); setAvatarVisible(row, true); row.sender_label.visible = false; // otherwise show the avatar } else { setAvatarVisible(row, true); } } _holdPosition() { this.__pos = this._vadj.get_upper() - this._vadj.get_value(); } _releasePosition() { this.__pos = 0; } _scrollPosition(pos = Gtk.PositionType.BOTTOM, animate = true) { let vpos = pos; this._vadj.freeze_notify(); if (pos === Gtk.PositionType.BOTTOM) vpos = this._vadj.get_upper() - this._vadj.get_page_size(); if (animate) { Tweener.addTween(this._vadj, { value: vpos, time: 0.5, transition: 'easeInOutCubic', onComplete: () => this._vadj.thaw_notify(), }); } else { GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { this._vadj.set_value(vpos); this._vadj.thaw_notify(); }); } } _sortMessages(row1, row2) { return (row1.message.date > row2.message.date) ? 1 : -1; } /** * Log the next message in the conversation. * * @param {Object} message - A message object */ logNext(message) { try { // TODO: Unsupported MessageBox if (message.type !== Sms.MessageBox.INBOX && message.type !== Sms.MessageBox.SENT) throw TypeError(`invalid message box ${message.type}`); // Append the message const row = this._createMessageRow(message); this.list.add(row); this.list.invalidate_headers(); // Remove the first pending message if (this.has_pending && message.type === Sms.MessageBox.SENT) { this.pending_box.get_children()[0].destroy(); this.notify('has-pending'); } } catch (e) { debug(e); } } /** * Log the previous message in the thread */ logPrevious() { try { const message = this.__messages.pop(); if (!message) return; // TODO: Unsupported MessageBox if (message.type !== Sms.MessageBox.INBOX && message.type !== Sms.MessageBox.SENT) throw TypeError(`invalid message box ${message.type}`); // Prepend the message const row = this._createMessageRow(message); this.list.prepend(row); this.list.invalidate_headers(); } catch (e) { debug(e); } } /** * Set the contents of the message entry * * @param {string} text - The message to place in the entry */ setMessage(text) { this.entry.text = text; this.entry.emit('move-cursor', 0, text.length, false); } }); /** * A ListBoxRow for a preview of a conversation */ const ConversationSummary = GObject.registerClass({ GTypeName: 'GSConnectMessagingConversationSummary', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/messaging-conversation-summary.ui', Children: ['avatar', 'name-label', 'time-label', 'body-label'], }, class ConversationSummary extends Gtk.ListBoxRow { _init(contacts, message) { super._init(); this.contacts = contacts; this.message = message; } get date() { return this._message.date; } get thread_id() { return this._message.thread_id; } get message() { return this._message; } set message(message) { this._message = message; this._sender = message.addresses[0].address || 'unknown'; // Contact Name let nameLabel = _('Unknown Contact'); // Update avatar for single-recipient messages if (message.addresses.length === 1) { this.avatar.contact = this.contacts[this._sender]; nameLabel = GLib.markup_escape_text(this.avatar.contact.name, -1); } else { this.avatar.contact = null; nameLabel = _('Group Message'); const participants = []; message.addresses.forEach((address) => { participants.push(this.contacts[address.address].name); }); this.name_label.tooltip_text = participants.join(', '); } // Contact Name & Message body let bodyLabel = message.body.split(/\r|\n/)[0]; bodyLabel = GLib.markup_escape_text(bodyLabel, -1); // Ignore the 'read' flag if it's an outgoing message if (message.type === Sms.MessageBox.SENT) { // TRANSLATORS: An outgoing message body in a conversation summary bodyLabel = _('You: %s').format(bodyLabel); // Otherwise make it bold if it's unread } else if (message.read === Sms.MessageStatus.UNREAD) { nameLabel = `<b>${nameLabel}</b>`; bodyLabel = `<b>${bodyLabel}</b>`; } // Set the labels, body always smaller this.name_label.label = nameLabel; this.body_label.label = `<small>${bodyLabel}</small>`; // Time const timeLabel = `<small>${getShortTime(message.date)}</small>`; this.time_label.label = timeLabel; } /** * Update the relative time label. */ update() { const timeLabel = `<small>${getShortTime(this.message.date)}</small>`; this.time_label.label = timeLabel; } }); /** * A Gtk.ApplicationWindow for SMS conversations */ export const Window = GObject.registerClass({ GTypeName: 'GSConnectMessagingWindow', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE, GObject.Object ), 'plugin': GObject.ParamSpec.object( 'plugin', 'Plugin', 'The plugin providing messages', GObject.ParamFlags.READWRITE, GObject.Object ), 'thread-id': GObject.ParamSpec.string( 'thread-id', 'Thread ID', 'The current thread', GObject.ParamFlags.READWRITE, '' ), }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/messaging-window.ui', Children: [ 'headerbar', 'infobar', 'thread-list', 'stack', ], }, class MessagingWindow extends Gtk.ApplicationWindow { _init(params) { super._init(params); this.headerbar.subtitle = this.device.name; this.insert_action_group('device', this.device); // Device Status this.device.bind_property( 'connected', this.infobar, 'reveal-child', GObject.BindingFlags.INVERT_BOOLEAN ); // Contacts this.contact_chooser = new Contacts.ContactChooser({ device: this.device, }); this.stack.add_named(this.contact_chooser, 'contact-chooser'); this._numberSelectedId = this.contact_chooser.connect( 'number-selected', this._onNumberSelected.bind(this) ); // Threads this.thread_list.set_sort_func(this._sortThreads); this._threadsChangedId = this.plugin.connect( 'notify::threads', this._onThreadsChanged.bind(this) ); this._timestampThreadsId = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT_IDLE, 60, this._timestampThreads.bind(this) ); this._sync(); this._onThreadsChanged(); this.restoreGeometry('messaging'); } vfunc_delete_event(event) { this.saveGeometry(); GLib.source_remove(this._timestampThreadsId); this.contact_chooser.disconnect(this._numberSelectedId); this.plugin.disconnect(this._threadsChangedId); return false; } get plugin() { return this._plugin || null; } set plugin(plugin) { this._plugin = plugin; } get thread_id() { return this.stack.visible_child_name; } set thread_id(thread_id) { thread_id = `${thread_id}`; // FIXME // Reset to the empty placeholder if (!thread_id) { this.thread_list.select_row(null); this.stack.set_visible_child_name('placeholder'); return; } // Create a conversation widget if there isn't one let conversation = this.stack.get_child_by_name(thread_id); const thread = this.plugin.threads[thread_id]; if (conversation === null) { if (!thread) { debug(`Thread ID ${thread_id} not found`); return; } conversation = new Conversation({ device: this.device, plugin: this.plugin, thread_id: thread_id, }); this.stack.add_named(conversation, thread_id); } // Figure out whether this is a multi-recipient thread this._setHeaderBar(thread[0].addresses); // Select the conversation and entry active this.stack.visible_child = conversation; this.stack.visible_child.entry.has_focus = true; // There was a pending message waiting for a conversation to be chosen if (this._pendingShare) { conversation.setMessage(this._pendingShare); this._pendingShare = null; } this._thread_id = thread_id; this.notify('thread_id'); } _setHeaderBar(addresses = []) { const address = addresses[0].address; const contact = this.device.contacts.query({number: address}); if (addresses.length === 1) { this.headerbar.title = contact.name; this.headerbar.subtitle = Contacts.getDisplayNumber(contact, address); } else { const otherLength = addresses.length - 1; this.headerbar.title = contact.name; this.headerbar.subtitle = ngettext( 'And %d other contact', 'And %d others', otherLength ).format(otherLength); } } _sync() { this.device.contacts.fetch(); this.plugin.connected(); } _onNewConversation() { this._sync(); this.stack.set_visible_child_name('contact-chooser'); this.thread_list.select_row(null); this.contact_chooser.entry.has_focus = true; } _onNumberSelected(chooser, number) { const contacts = chooser.getSelected(); const row = this._getRowForContacts(contacts); if (row) row.emit('activate'); else this.setContacts(contacts); } /** * Threads */ _onThreadsChanged() { // Get the last message in each thread const messages = {}; for (const [thread_id, thread] of Object.entries(this.plugin.threads)) { const message = thread[thread.length - 1]; // Skip messages without a body (eg. MMS messages without text) if (message.body) messages[thread_id] = thread[thread.length - 1]; } // Update existing summaries and destroy old ones for (const row of this.thread_list.get_children()) { const message = messages[row.thread_id]; // If it's an existing conversation, update it if (message) { // Ensure there's a contact mapping const sender = message.addresses[0].address || 'unknown'; if (row.contacts[sender] === undefined) { row.contacts[sender] = this.device.contacts.query({ number: sender, }); } row.message = message; delete messages[row.thread_id]; // Otherwise destroy it } else { // Destroy the conversation widget const conversation = this.stack.get_child_by_name(`${row.thread_id}`); if (conversation) { conversation.destroy(); system.gc(); } // Then the summary widget row.destroy(); // HACK: temporary mitigator for mysterious GtkListBox leak system.gc(); } } // What's left in the dictionary is new summaries for (const message of Object.values(messages)) { const contacts = this.device.contacts.lookupAddresses(message.addresses); const conversation = new ConversationSummary(contacts, message); this.thread_list.add(conversation); } // Re-sort the summaries this.thread_list.invalidate_sort(); } // GtkListBox::row-activated _onThreadSelected(box, row) { // Show the conversation for this number (if applicable) if (row) { this.thread_id = row.thread_id; // Show the placeholder } else { this.headerbar.title = _('Messaging'); this.headerbar.subtitle = this.device.name; } } _sortThreads(row1, row2) { return (row1.date > row2.date) ? -1 : 1; } _timestampThreads() { if (this.visible) this.thread_list.foreach(row => row.update()); return GLib.SOURCE_CONTINUE; } /** * Find the thread row for @contacts * * @param {Object[]} contacts - A contact group * @return {ConversationSummary|null} The thread row or %null */ _getRowForContacts(contacts) { const addresses = Object.keys(contacts).map(address => { return {address: address}; }); // Try to find a thread_id const thread_id = this.plugin.getThreadIdForAddresses(addresses); for (const row of this.thread_list.get_children()) { if (row.message.thread_id === thread_id) return row; } return null; } setContacts(contacts) { // Group the addresses const addresses = []; for (const address of Object.keys(contacts)) addresses.push({address: address}); // Try to find a thread ID for this address group let thread_id = this.plugin.getThreadIdForAddresses(addresses); if (thread_id === null) thread_id = GLib.uuid_string_random(); else thread_id = thread_id.toString(); // Try to find a thread row for the ID const row = this._getRowForContacts(contacts); if (row !== null) { this.thread_list.select_row(row); return; } // We're creating a new conversation const conversation = new Conversation({ device: this.device, plugin: this.plugin, addresses: addresses, }); // Set the headerbar this._setHeaderBar(addresses); // Select the conversation and entry active this.stack.add_named(conversation, thread_id); this.stack.visible_child = conversation; this.stack.visible_child.entry.has_focus = true; // There was a pending message waiting for a conversation to be chosen if (this._pendingShare) { conversation.setMessage(this._pendingShare); this._pendingShare = null; } this._thread_id = thread_id; this.notify('thread-id'); } _includesAddress(addresses, addressObj) { const number = addressObj.address.toPhoneNumber(); for (const haystackObj of addresses) { const tnumber = haystackObj.address.toPhoneNumber(); if (number.endsWith(tnumber) || tnumber.endsWith(number)) return true; } return false; } /** * Try and find an existing conversation widget for @message. * * @param {Object} message - A message object * @return {Conversation|null} A conversation widget or %null */ getConversationForMessage(message) { // TODO: This shouldn't happen? if (message === null) return null; // First try to find a conversation by thread_id const thread_id = `${message.thread_id}`; const conversation = this.stack.get_child_by_name(thread_id); if (conversation !== null) return conversation; // Try and find one by matching addresses, which is necessary if we've // started a thread locally and haven't set the thread_id const addresses = message.addresses; for (const conversation of this.stack.get_children()) { if (conversation.addresses === undefined || conversation.addresses.length !== addresses.length) continue; const caddrs = conversation.addresses; // If we find a match, set `thread-id` on the conversation and the // child property `name`. if (addresses.every(addr => this._includesAddress(caddrs, addr))) { conversation._thread_id = thread_id; this.stack.child_set_property(conversation, 'name', thread_id); return conversation; } } return null; } /** * Set the contents of the message entry. If @pending is %false set the * message of the currently selected conversation, otherwise mark the * message to be set for the next selected conversation. * * @param {string} message - The message to place in the entry * @param {boolean} pending - Wait for a conversation to be selected */ setMessage(message, pending = false) { try { if (pending) this._pendingShare = message; else this.stack.visible_child.setMessage(message); } catch (e) { debug(e); } } }); /** * A Gtk.ApplicationWindow for selecting from open conversations */ export const ConversationChooser = GObject.registerClass({ GTypeName: 'GSConnectConversationChooser', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE, GObject.Object ), 'message': GObject.ParamSpec.string( 'message', 'Message', 'The message to share', GObject.ParamFlags.READWRITE, '' ), 'plugin': GObject.ParamSpec.object( 'plugin', 'Plugin', 'The plugin providing messages', GObject.ParamFlags.READWRITE, GObject.Object ), }, }, class ConversationChooser extends Gtk.ApplicationWindow { _init(params) { super._init(Object.assign({ title: _('Share Link'), default_width: 300, default_height: 200, }, params)); this.set_keep_above(true); // HeaderBar this.headerbar = new Gtk.HeaderBar({ title: _('Share Link'), subtitle: this.message, show_close_button: true, tooltip_text: this.message, }); this.set_titlebar(this.headerbar); const newButton = new Gtk.Button({ image: new Gtk.Image({icon_name: 'list-add-symbolic'}), tooltip_text: _('New Conversation'), always_show_image: true, }); newButton.connect('clicked', this._new.bind(this)); this.headerbar.pack_start(newButton); // Threads const scrolledWindow = new Gtk.ScrolledWindow({ can_focus: false, hexpand: true, vexpand: true, hscrollbar_policy: Gtk.PolicyType.NEVER, }); this.add(scrolledWindow); this.thread_list = new Gtk.ListBox({ activate_on_single_click: false, }); this.thread_list.set_sort_func(Window.prototype._sortThreads); this.thread_list.connect('row-activated', this._select.bind(this)); scrolledWindow.add(this.thread_list); // Filter Setup Window.prototype._onThreadsChanged.call(this); this.show_all(); } get plugin() { return this._plugin || null; } set plugin(plugin) { this._plugin = plugin; } _new(button) { const message = this.message; this.destroy(); this.plugin.sms(); this.plugin.window._onNewConversation(); this.plugin.window._pendingShare = message; } _select(box, row) { this.plugin.sms(); this.plugin.window.thread_id = row.message.thread_id.toString(); this.plugin.window.setMessage(this.message); this.destroy(); } }); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/ui/mousepad.js������������������������0000664�0000000�0000000�00000032070�14607666711�0027065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import Gdk from 'gi://Gdk'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; /** * A map of Gdk to "KDE Connect" keyvals */ const ReverseKeyMap = new Map([ [Gdk.KEY_BackSpace, 1], [Gdk.KEY_Tab, 2], [Gdk.KEY_Linefeed, 3], [Gdk.KEY_Left, 4], [Gdk.KEY_Up, 5], [Gdk.KEY_Right, 6], [Gdk.KEY_Down, 7], [Gdk.KEY_Page_Up, 8], [Gdk.KEY_Page_Down, 9], [Gdk.KEY_Home, 10], [Gdk.KEY_End, 11], [Gdk.KEY_Return, 12], [Gdk.KEY_Delete, 13], [Gdk.KEY_Escape, 14], [Gdk.KEY_Sys_Req, 15], [Gdk.KEY_Scroll_Lock, 16], [Gdk.KEY_F1, 21], [Gdk.KEY_F2, 22], [Gdk.KEY_F3, 23], [Gdk.KEY_F4, 24], [Gdk.KEY_F5, 25], [Gdk.KEY_F6, 26], [Gdk.KEY_F7, 27], [Gdk.KEY_F8, 28], [Gdk.KEY_F9, 29], [Gdk.KEY_F10, 30], [Gdk.KEY_F11, 31], [Gdk.KEY_F12, 32], ]); /* * A list of keyvals we consider modifiers */ const MOD_KEYS = [ Gdk.KEY_Alt_L, Gdk.KEY_Alt_R, Gdk.KEY_Caps_Lock, Gdk.KEY_Control_L, Gdk.KEY_Control_R, Gdk.KEY_Meta_L, Gdk.KEY_Meta_R, Gdk.KEY_Num_Lock, Gdk.KEY_Shift_L, Gdk.KEY_Shift_R, Gdk.KEY_Super_L, Gdk.KEY_Super_R, ]; /* * Some convenience functions for checking keyvals for modifiers */ const isAlt = (key) => [Gdk.KEY_Alt_L, Gdk.KEY_Alt_R].includes(key); const isCtrl = (key) => [Gdk.KEY_Control_L, Gdk.KEY_Control_R].includes(key); const isShift = (key) => [Gdk.KEY_Shift_L, Gdk.KEY_Shift_R].includes(key); const isSuper = (key) => [Gdk.KEY_Super_L, Gdk.KEY_Super_R].includes(key); export const InputDialog = GObject.registerClass({ GTypeName: 'GSConnectMousepadInputDialog', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE, GObject.Object ), 'plugin': GObject.ParamSpec.object( 'plugin', 'Plugin', 'The mousepad plugin associated with this window', GObject.ParamFlags.READWRITE, GObject.Object ), }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/mousepad-input-dialog.ui', Children: [ 'infobar', 'infobar-label', 'touchpad-eventbox', 'mouse-left-button', 'mouse-middle-button', 'mouse-right-button', 'touchpad-drag', 'touchpad-long-press', 'shift-label', 'ctrl-label', 'alt-label', 'super-label', 'entry', ], }, class InputDialog extends Gtk.Dialog { _init(params) { super._init(Object.assign({ use_header_bar: true, }, params)); const headerbar = this.get_titlebar(); headerbar.title = _('Remote Input'); headerbar.subtitle = this.device.name; // Main Box const content = this.get_content_area(); content.border_width = 0; // TRANSLATORS: Displayed when the remote keyboard is not ready to accept input this.infobar_label.label = _('Remote keyboard on %s is not active').format(this.device.name); // Text Input this.entry.buffer.connect( 'insert-text', this._onInsertText.bind(this) ); this.infobar.connect('notify::reveal-child', this._onState.bind(this)); this.plugin.bind_property('state', this.infobar, 'reveal-child', 6); // Mouse Pad this._resetTouchpadMotion(); this.touchpad_motion_timeout_id = 0; this.touchpad_holding = false; // Scroll Input this.add_events(Gdk.EventMask.SCROLL_MASK); this.show_all(); } vfunc_delete_event(event) { this._ungrab(); return this.hide_on_delete(); } vfunc_grab_broken_event(event) { if (event.keyboard) this._ungrab(); return false; } vfunc_key_release_event(event) { if (!this.plugin.state) debug('ignoring remote keyboard state'); const keyvalLower = Gdk.keyval_to_lower(event.keyval); const realMask = event.state & Gtk.accelerator_get_default_mod_mask(); this.alt_label.sensitive = !isAlt(keyvalLower) && (realMask & Gdk.ModifierType.MOD1_MASK); this.ctrl_label.sensitive = !isCtrl(keyvalLower) && (realMask & Gdk.ModifierType.CONTROL_MASK); this.shift_label.sensitive = !isShift(keyvalLower) && (realMask & Gdk.ModifierType.SHIFT_MASK); this.super_label.sensitive = !isSuper(keyvalLower) && (realMask & Gdk.ModifierType.SUPER_MASK); return super.vfunc_key_release_event(event); } vfunc_key_press_event(event) { if (!this.plugin.state) debug('ignoring remote keyboard state'); let keyvalLower = Gdk.keyval_to_lower(event.keyval); let realMask = event.state & Gtk.accelerator_get_default_mod_mask(); this.alt_label.sensitive = isAlt(keyvalLower) || (realMask & Gdk.ModifierType.MOD1_MASK); this.ctrl_label.sensitive = isCtrl(keyvalLower) || (realMask & Gdk.ModifierType.CONTROL_MASK); this.shift_label.sensitive = isShift(keyvalLower) || (realMask & Gdk.ModifierType.SHIFT_MASK); this.super_label.sensitive = isSuper(keyvalLower) || (realMask & Gdk.ModifierType.SUPER_MASK); // Wait for a real key before sending if (MOD_KEYS.includes(keyvalLower)) return false; // Normalize Tab if (keyvalLower === Gdk.KEY_ISO_Left_Tab) keyvalLower = Gdk.KEY_Tab; // Put shift back if it changed the case of the key, not otherwise. if (keyvalLower !== event.keyval) realMask |= Gdk.ModifierType.SHIFT_MASK; // HACK: we don't want to use SysRq as a keybinding (but we do want // Alt+Print), so we avoid translation from Alt+Print to SysRq if (keyvalLower === Gdk.KEY_Sys_Req && (realMask & Gdk.ModifierType.MOD1_MASK) !== 0) keyvalLower = Gdk.KEY_Print; // CapsLock isn't supported as a keybinding modifier, so keep it from // confusing us realMask &= ~Gdk.ModifierType.LOCK_MASK; if (keyvalLower === 0) return false; debug(`keyval: ${event.keyval}, mask: ${realMask}`); const request = { alt: !!(realMask & Gdk.ModifierType.MOD1_MASK), ctrl: !!(realMask & Gdk.ModifierType.CONTROL_MASK), shift: !!(realMask & Gdk.ModifierType.SHIFT_MASK), super: !!(realMask & Gdk.ModifierType.SUPER_MASK), sendAck: true, }; // specialKey if (ReverseKeyMap.has(event.keyval)) { request.specialKey = ReverseKeyMap.get(event.keyval); // key } else { const codePoint = Gdk.keyval_to_unicode(event.keyval); request.key = String.fromCodePoint(codePoint); } this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: request, }); // Pass these key combinations rather than using the echo reply if (request.alt || request.ctrl || request.super) return super.vfunc_key_press_event(event); return false; } vfunc_scroll_event(event) { if (event.delta_x === 0 && event.delta_y === 0) return true; this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { scroll: true, dx: event.delta_x * 200, dy: event.delta_y * 200, }, }); return true; } vfunc_window_state_event(event) { if (!this.plugin.state) debug('ignoring remote keyboard state'); if (event.new_window_state & Gdk.WindowState.FOCUSED) this._grab(); else this._ungrab(); return super.vfunc_window_state_event(event); } _onInsertText(buffer, location, text, len) { if (this._isAck) return; debug(`insert-text: ${text} (chars ${[...text].length})`); for (const char of [...text]) { if (!char) continue; // TODO: modifiers? this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { alt: false, ctrl: false, shift: false, super: false, sendAck: false, key: char, }, }); } } _onState(widget) { if (!this.plugin.state) debug('ignoring remote keyboard state'); if (this.is_active) this._grab(); else this._ungrab(); } _grab() { if (!this.visible || this._keyboard) return; const seat = Gdk.Display.get_default().get_default_seat(); const status = seat.grab( this.get_window(), Gdk.SeatCapabilities.KEYBOARD, false, null, null, null ); if (status !== Gdk.GrabStatus.SUCCESS) { logError(new Error('Grabbing keyboard failed')); return; } this._keyboard = seat.get_keyboard(); this.grab_add(); this.entry.has_focus = true; } _ungrab() { if (this._keyboard) { this._keyboard.get_seat().ungrab(); this._keyboard = null; this.grab_remove(); } this.entry.buffer.text = ''; } _resetTouchpadMotion() { this.touchpad_motion_prev_x = 0; this.touchpad_motion_prev_y = 0; this.touchpad_motion_x = 0; this.touchpad_motion_y = 0; } _onMouseLeftButtonClicked(button) { this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { singleclick: true, }, }); } _onMouseMiddleButtonClicked(button) { this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { middleclick: true, }, }); } _onMouseRightButtonClicked(button) { this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { rightclick: true, }, }); } _onTouchpadDragBegin(gesture) { this._resetTouchpadMotion(); this.touchpad_motion_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 10, this._onTouchpadMotionTimeout.bind(this)); } _onTouchpadDragUpdate(gesture, offset_x, offset_y) { this.touchpad_motion_x = offset_x; this.touchpad_motion_y = offset_y; } _onTouchpadDragEnd(gesture) { this._resetTouchpadMotion(); GLib.Source.remove(this.touchpad_motion_timeout_id); this.touchpad_motion_timeout_id = 0; } _onTouchpadLongPressCancelled(gesture) { const gesture_button = gesture.get_current_button(); // Check user dragged less than certain distances. const is_click = (Math.abs(this.touchpad_motion_x) < 4) && (Math.abs(this.touchpad_motion_y) < 4); if (is_click) { const click_body = {}; switch (gesture_button) { case 1: click_body.singleclick = true; break; case 2: click_body.middleclick = true; break; case 3: click_body.rightclick = true; break; default: return; } this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: click_body, }); } } _onTouchpadLongPressPressed(gesture) { const gesture_button = gesture.get_current_button(); if (gesture_button !== 1) { debug('Long press on other type of buttons are not handled.'); } else { this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { singlehold: true, }, }); this.touchpad_holding = true; } } _onTouchpadLongPressEnd(gesture) { if (this.touchpad_holding) { this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { singlerelease: true, }, }); this.touchpad_holding = false; } } _onTouchpadMotionTimeout() { const diff_x = this.touchpad_motion_x - this.touchpad_motion_prev_x; const diff_y = this.touchpad_motion_y - this.touchpad_motion_prev_y; this.device.sendPacket({ type: 'kdeconnect.mousepad.request', body: { dx: diff_x, dy: diff_y, }, }); this.touchpad_motion_prev_x = this.touchpad_motion_x; this.touchpad_motion_prev_y = this.touchpad_motion_y; return true; } }); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/ui/notification.js��������������������0000664�0000000�0000000�00000010754�14607666711�0027743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import * as URI from '../utils/uri.js'; import '../utils/ui.js'; /** * A dialog for repliable notifications. */ const ReplyDialog = GObject.registerClass({ GTypeName: 'GSConnectNotificationReplyDialog', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE, GObject.Object ), 'plugin': GObject.ParamSpec.object( 'plugin', 'Plugin', 'The plugin that owns this notification', GObject.ParamFlags.READWRITE, GObject.Object ), 'uuid': GObject.ParamSpec.string( 'uuid', 'UUID', 'The notification reply UUID', GObject.ParamFlags.READWRITE, null ), }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/notification-reply-dialog.ui', Children: ['infobar', 'notification-title', 'notification-body', 'entry'], }, class ReplyDialog extends Gtk.Dialog { _init(params) { super._init({ application: Gio.Application.get_default(), device: params.device, plugin: params.plugin, uuid: params.uuid, use_header_bar: true, }); this.set_response_sensitive(Gtk.ResponseType.OK, false); // Info bar this.device.bind_property( 'connected', this.infobar, 'reveal-child', GObject.BindingFlags.INVERT_BOOLEAN ); // Notification Data const headerbar = this.get_titlebar(); headerbar.title = params.notification.appName; headerbar.subtitle = this.device.name; this.notification_title.label = params.notification.title; this.notification_body.label = URI.linkify(params.notification.text); // Message Entry/Send Button this.device.bind_property( 'connected', this.entry, 'sensitive', GObject.BindingFlags.DEFAULT ); this._connectedId = this.device.connect( 'notify::connected', this._onStateChanged.bind(this) ); this._entryChangedId = this.entry.buffer.connect( 'changed', this._onStateChanged.bind(this) ); this.restoreGeometry('notification-reply-dialog'); this.connect('destroy', this._onDestroy); } _onDestroy(dialog) { dialog.entry.buffer.disconnect(dialog._entryChangedId); dialog.device.disconnect(dialog._connectedId); } vfunc_delete_event() { this.saveGeometry(); return false; } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { // Refuse to send empty or whitespace only messages if (!this.entry.buffer.text.trim()) return; this.plugin.replyNotification( this.uuid, this.entry.buffer.text ); } this.destroy(); } get device() { if (this._device === undefined) this._device = null; return this._device; } set device(device) { this._device = device; } get plugin() { if (this._plugin === undefined) this._plugin = null; return this._plugin; } set plugin(plugin) { this._plugin = plugin; } get uuid() { if (this._uuid === undefined) this._uuid = null; return this._uuid; } set uuid(uuid) { this._uuid = uuid; // We must have a UUID if (!uuid) { this.destroy(); debug('no uuid for repliable notification'); } } _onActivateLink(label, uri) { Gtk.show_uri_on_window( this.get_toplevel(), uri.includes('://') ? uri : `https://${uri}`, Gtk.get_current_event_time() ); return true; } _onStateChanged() { if (this.device.connected && this.entry.buffer.text.trim()) this.set_response_sensitive(Gtk.ResponseType.OK, true); else this.set_response_sensitive(Gtk.ResponseType.OK, false); } }); export default ReplyDialog; ��������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/ui/service.js�������������������������0000664�0000000�0000000�00000015436�14607666711�0026717�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import system from 'system'; import Config from '../../config.js'; /* * Issue Header */ const ISSUE_HEADER = ` GSConnect: ${Config.PACKAGE_VERSION} (${Config.IS_USER ? 'user' : 'system'}) GJS: ${system.version} Session: ${GLib.getenv('XDG_SESSION_TYPE')} OS: ${GLib.get_os_info('PRETTY_NAME')} `; /** * A dialog for selecting a device */ export const DeviceChooser = GObject.registerClass({ GTypeName: 'GSConnectServiceDeviceChooser', Properties: { 'action-name': GObject.ParamSpec.string( 'action-name', 'Action Name', 'The name of the associated action, like "sendFile"', GObject.ParamFlags.READWRITE, null ), 'action-target': GObject.param_spec_variant( 'action-target', 'Action Target', 'The parameter for action invocations', new GLib.VariantType('*'), null, GObject.ParamFlags.READWRITE ), }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/service-device-chooser.ui', Children: ['device-list', 'cancel-button', 'select-button'], }, class DeviceChooser extends Gtk.Dialog { _init(params = {}) { super._init({ use_header_bar: true, application: Gio.Application.get_default(), }); this.set_keep_above(true); // HeaderBar this.get_header_bar().subtitle = params.title; // Dialog Action this.action_name = params.action_name; this.action_target = params.action_target; // Device List this.device_list.set_sort_func(this._sortDevices); this._devicesChangedId = this.application.settings.connect( 'changed::devices', this._onDevicesChanged.bind(this) ); this._onDevicesChanged(); } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { try { const device = this.device_list.get_selected_row().device; device.activate_action(this.action_name, this.action_target); } catch (e) { logError(e); } } this.destroy(); } get action_name() { if (this._action_name === undefined) this._action_name = null; return this._action_name; } set action_name(name) { this._action_name = name; } get action_target() { if (this._action_target === undefined) this._action_target = null; return this._action_target; } set action_target(variant) { this._action_target = variant; } _onDeviceActivated(box, row) { this.response(Gtk.ResponseType.OK); } _onDeviceSelected(box) { this.set_response_sensitive( Gtk.ResponseType.OK, (box.get_selected_row()) ); } _onDevicesChanged() { // Collect known devices const devices = {}; for (const [id, device] of this.application.manager.devices.entries()) devices[id] = device; // Prune device rows this.device_list.foreach(row => { if (!devices.hasOwnProperty(row.name)) row.destroy(); else delete devices[row.name]; }); // Add new devices for (const device of Object.values(devices)) { const action = device.lookup_action(this.action_name); if (action === null) continue; const row = new Gtk.ListBoxRow({ visible: action.enabled, }); row.set_name(device.id); row.device = device; action.bind_property( 'enabled', row, 'visible', Gio.SettingsBindFlags.DEFAULT ); const grid = new Gtk.Grid({ column_spacing: 12, margin: 6, visible: true, }); row.add(grid); const icon = new Gtk.Image({ icon_name: device.icon_name, pixel_size: 32, visible: true, }); grid.attach(icon, 0, 0, 1, 1); const name = new Gtk.Label({ label: device.name, halign: Gtk.Align.START, hexpand: true, visible: true, }); grid.attach(name, 1, 0, 1, 1); this.device_list.add(row); } if (this.device_list.get_selected_row() === null) this.device_list.select_row(this.device_list.get_row_at_index(0)); } _sortDevices(row1, row2) { return row1.device.name.localeCompare(row2.device.name); } }); /** * A dialog for reporting an error. */ export const ErrorDialog = GObject.registerClass({ GTypeName: 'GSConnectServiceErrorDialog', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/service-error-dialog.ui', Children: [ 'error-stack', 'expander-arrow', 'gesture', 'report-button', 'revealer', ], }, class ErrorDialog extends Gtk.Window { _init(error) { super._init({ application: Gio.Application.get_default(), title: `GSConnect: ${error.name}`, }); this.set_keep_above(true); this.error = error; this.error_stack.buffer.text = `${error.message}\n\n${error.stack}`; this.gesture.connect('released', this._onReleased.bind(this)); } _onClicked(button) { if (this.report_button === button) { const uri = this._buildUri(this.error.message, this.error.stack); Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null); } this.destroy(); } _onReleased(gesture, n_press) { if (n_press === 1) this.revealer.reveal_child = !this.revealer.reveal_child; } _onRevealChild(revealer, pspec) { this.expander_arrow.icon_name = this.revealer.reveal_child ? 'pan-down-symbolic' : 'pan-end-symbolic'; } _buildUri(message, stack) { const body = `\`\`\`${ISSUE_HEADER}\n${stack}\n\`\`\``; const titleQuery = encodeURIComponent(message).replace('%20', '+'); const bodyQuery = encodeURIComponent(body).replace('%20', '+'); const uri = `${Config.PACKAGE_BUGREPORT}?title=${titleQuery}&body=${bodyQuery}`; // Reasonable URI length limit if (uri.length > 2000) return uri.substr(0, 2000); return uri; } }); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/utils/��������������������������������0000775�0000000�0000000�00000000000�14607666711�0025433�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/utils/dbus.js�������������������������0000664�0000000�0000000�00000017252�14607666711�0026735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GjsPrivate from 'gi://GjsPrivate'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; /* * Some utility methods */ function toDBusCase(string) { return string.replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, offset) => { return ltr.toUpperCase(); }).replace(/[\s_-]+/g, ''); } function toUnderscoreCase(string) { return string.replace(/(?:^\w|[A-Z]|_|\b\w)/g, (ltr, offset) => { if (ltr === '_') return ''; return (offset > 0) ? `_${ltr.toLowerCase()}` : ltr.toLowerCase(); }).replace(/[\s-]+/g, ''); } /** * DBus.Interface represents a DBus interface bound to an object instance, meant * to be exported over DBus. */ export const Interface = GObject.registerClass({ GTypeName: 'GSConnectDBusInterface', Implements: [Gio.DBusInterface], Properties: { 'g-instance': GObject.ParamSpec.object( 'g-instance', 'Instance', 'The delegate GObject', GObject.ParamFlags.READWRITE, GObject.Object.$gtype ), }, }, class Interface extends GjsPrivate.DBusImplementation { _init(params) { super._init({ g_instance: params.g_instance, g_interface_info: params.g_interface_info, }); // Cache member lookups this._instanceHandlers = []; this._instanceMethods = {}; this._instanceProperties = {}; const info = this.get_info(); this.connect('handle-method-call', this._call.bind(this._instance, info)); this.connect('handle-property-get', this._get.bind(this._instance, info)); this.connect('handle-property-set', this._set.bind(this._instance, info)); // Automatically forward known signals const id = this._instance.connect('notify', this._notify.bind(this)); this._instanceHandlers.push(id); for (const signal of info.signals) { const type = `(${signal.args.map(arg => arg.signature).join('')})`; const id = this._instance.connect( signal.name, this._emit.bind(this, signal.name, type) ); this._instanceHandlers.push(id); } // Export if connection and object path were given if (params.g_connection && params.g_object_path) this.export(params.g_connection, params.g_object_path); } get g_instance() { if (this._instance === undefined) this._instance = null; return this._instance; } set g_instance(instance) { this._instance = instance; } /** * Invoke an instance's method for a DBus method call. * * @param {Gio.DBusInterfaceInfo} info - The DBus interface * @param {DBus.Interface} iface - The DBus interface * @param {string} name - The DBus method name * @param {GLib.Variant} parameters - The method parameters * @param {Gio.DBusMethodInvocation} invocation - The method invocation info */ async _call(info, iface, name, parameters, invocation) { let retval; // Invoke the instance method try { const args = parameters.unpack().map(parameter => { if (parameter.get_type_string() === 'h') { const message = invocation.get_message(); const fds = message.get_unix_fd_list(); const idx = parameter.deepUnpack(); return fds.get(idx); } else { return parameter.recursiveUnpack(); } }); retval = await this[name](...args); } catch (e) { if (e instanceof GLib.Error) { invocation.return_gerror(e); } else { // likely to be a normal JS error if (!e.name.includes('.')) e.name = `org.gnome.gjs.JSError.${e.name}`; invocation.return_dbus_error(e.name, e.message); } logError(e, `${this}: ${name}`); return; } // `undefined` is an empty tuple on DBus if (retval === undefined) retval = new GLib.Variant('()', []); // Return the instance result or error try { if (!(retval instanceof GLib.Variant)) { const args = info.lookup_method(name).out_args; retval = new GLib.Variant( `(${args.map(arg => arg.signature).join('')})`, (args.length === 1) ? [retval] : retval ); } invocation.return_value(retval); } catch (e) { invocation.return_dbus_error( 'org.gnome.gjs.JSError.ValueError', 'Service implementation returned an incorrect value type' ); logError(e, `${this}: ${name}`); } } _nativeProp(obj, name) { if (this._instanceProperties[name] === undefined) { let propName = name; if (propName in obj) this._instanceProperties[name] = propName; if (this._instanceProperties[name] === undefined) { propName = toUnderscoreCase(name); if (propName in obj) this._instanceProperties[name] = propName; } } return this._instanceProperties[name]; } _emit(name, type, obj, ...args) { this.emit_signal(name, new GLib.Variant(type, args)); } _get(info, iface, name) { const nativeValue = this[iface._nativeProp(this, name)]; const propertyInfo = info.lookup_property(name); if (nativeValue === undefined || propertyInfo === null) return null; return new GLib.Variant(propertyInfo.signature, nativeValue); } _set(info, iface, name, value) { const nativeValue = value.recursiveUnpack(); this[iface._nativeProp(this, name)] = nativeValue; } _notify(obj, pspec) { const name = toDBusCase(pspec.name); const propertyInfo = this.get_info().lookup_property(name); if (propertyInfo === null) return; this.emit_property_changed( name, new GLib.Variant( propertyInfo.signature, // Adjust for GJS's '-'/'_' conversion this._instance[pspec.name.replace(/-/gi, '_')] ) ); } destroy() { try { for (const id of this._instanceHandlers) this._instance.disconnect(id); this._instanceHandlers = []; this.flush(); this.unexport(); } catch (e) { logError(e); } } }); /** * Get a new, dedicated DBus connection on @busType * * @param {Gio.BusType} [busType] - a Gio.BusType constant * @param {Gio.Cancellable} [cancellable] - an optional Gio.Cancellable * @return {Promise<Gio.DBusConnection>} A new DBus connection */ export function newConnection(busType = Gio.BusType.SESSION, cancellable = null) { return new Promise((resolve, reject) => { Gio.DBusConnection.new_for_address( Gio.dbus_address_get_for_bus_sync(busType, cancellable), Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT | Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION, null, cancellable, (connection, res) => { try { resolve(Gio.DBusConnection.new_for_address_finish(res)); } catch (e) { reject(e); } } ); }); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/utils/ui.js���������������������������0000664�0000000�0000000�00000002620�14607666711�0026406�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Gtk from 'gi://Gtk'; import Config from '../../config.js'; /* * Window State */ Gtk.Window.prototype.restoreGeometry = function (context = 'default') { this._windowState = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect.WindowState', true ), path: `/org/gnome/shell/extensions/gsconnect/${context}/`, }); // Size const [width, height] = this._windowState.get_value('window-size').deepUnpack(); if (width && height) this.set_default_size(width, height); // Maximized State if (this._windowState.get_boolean('window-maximized')) this.maximize(); }; Gtk.Window.prototype.saveGeometry = function () { const state = this.get_window().get_state(); // Maximized State const maximized = (state & Gdk.WindowState.MAXIMIZED); this._windowState.set_boolean('window-maximized', maximized); // Leave the size at the value before maximizing if (maximized || (state & Gdk.WindowState.FULLSCREEN)) return; // Size const size = this.get_size(); this._windowState.set_value('window-size', new GLib.Variant('(ii)', size)); }; ����������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/service/utils/uri.js��������������������������0000664�0000000�0000000�00000013037�14607666711�0026574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import GLib from 'gi://GLib'; /** * The same regular expression used in GNOME Shell * * http://daringfireball.net/2010/07/improved_regex_for_matching_urls */ const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)'; const _leadingJunk = '[\\s`(\\[{\'\\"<\u00AB\u201C\u2018]'; const _notTrailingJunk = '[^\\s`!()\\[\\]{};:\'\\".,<>?\u00AB\u00BB\u201C\u201D\u2018\u2019]'; const _urlRegexp = new RegExp( '(^|' + _leadingJunk + ')' + '(' + '(?:' + '(?:http|https)://' + // scheme:// '|' + 'www\\d{0,3}[.]' + // www. '|' + '[a-z0-9.\\-]+[.][a-z]{2,4}/' + // foo.xx/ ')' + '(?:' + // one or more: '[^\\s()<>]+' + // run of non-space non-() '|' + // or _balancedParens + // balanced parens ')+' + '(?:' + // end with: _balancedParens + // balanced parens '|' + // or _notTrailingJunk + // last non-junk char ')' + ')', 'gi'); /** * sms/tel URI RegExp (https://tools.ietf.org/html/rfc5724) * * A fairly lenient regexp for sms: URIs that allows tel: numbers with chars * from global-number, local-number (without phone-context) and single spaces. * This allows passing numbers directly from libfolks or GData without * pre-processing. It also makes an allowance for URIs passed from Gio.File * that always come in the form "sms:///". */ const _smsParam = "[\\w.!~*'()-]+=(?:[\\w.!~*'()-]|%[0-9A-F]{2})*"; const _telParam = ";[a-zA-Z0-9-]+=(?:[\\w\\[\\]/:&+$.!~*'()-]|%[0-9A-F]{2})+"; const _lenientDigits = '[+]?(?:[0-9A-F*#().-]| (?! )|%20(?!%20))+'; const _lenientNumber = `${_lenientDigits}(?:${_telParam})*`; const _smsRegex = new RegExp( '^' + 'sms:' + // scheme '(?:[/]{2,3})?' + // Gio.File returns ":///" '(' + // one or more... _lenientNumber + // phone numbers '(?:,' + _lenientNumber + ')*' + // separated by commas ')' + '(?:\\?(' + // followed by optional... _smsParam + // parameters... '(?:&' + _smsParam + ')*' + // separated by "&" (unescaped) '))?' + '$', 'g'); // fragments (#foo) not allowed const _numberRegex = new RegExp( '^' + '(' + _lenientDigits + ')' + // phone number digits '((?:' + _telParam + ')*)' + // followed by optional parameters '$', 'g'); /** * Searches @str for URLs and returns an array of objects with %url * properties showing the matched URL string, and %pos properties indicating * the position within @str where the URL was found. * * @param {string} str - the string to search * @return {Object[]} the list of match objects, as described above */ export function findUrls(str) { _urlRegexp.lastIndex = 0; const res = []; let match; while ((match = _urlRegexp.exec(str))) { const name = match[2]; const url = GLib.uri_parse_scheme(name) ? name : `http://${name}`; res.push({name, url, pos: match.index + match[1].length}); } return res; } /** * Return a string with URLs couched in <a> tags, parseable by Pango and * using the same RegExp as GNOME Shell. * * @param {string} str - The string to be modified * @param {string} [title] - An optional title (eg. alt text, tooltip) * @return {string} the modified text */ export function linkify(str, title = null) { const text = GLib.markup_escape_text(str, -1); _urlRegexp.lastIndex = 0; if (title) { return text.replace( _urlRegexp, `$1<a href="$2" title="${title}">$2</a>` ); } else { return text.replace(_urlRegexp, '$1<a href="$2">$2</a>'); } } /** * A simple parsing class for sms: URI's (https://tools.ietf.org/html/rfc5724) */ export default class URI { constructor(uri) { _smsRegex.lastIndex = 0; const [, recipients, query] = _smsRegex.exec(uri); this.recipients = recipients.split(',').map(recipient => { _numberRegex.lastIndex = 0; const [, number, params] = _numberRegex.exec(recipient); if (params) { for (const param of params.substr(1).split(';')) { const [key, value] = param.split('='); // add phone-context to beginning of if (key === 'phone-context' && value.startsWith('+')) return value + unescape(number); } } return unescape(number); }); if (query) { for (const field of query.split('&')) { const [key, value] = field.split('='); if (key === 'body') { if (this.body) throw URIError('duplicate "body" field'); this.body = value ? decodeURIComponent(value) : undefined; } } } } toString() { const uri = `sms:${this.recipients.join(',')}`; return this.body ? `${uri}?body=${escape(this.body)}` : uri; } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/����������������������������������������0000775�0000000�0000000�00000000000�14607666711�0023742�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/clipboard.js����������������������������0000664�0000000�0000000�00000025061�14607666711�0026243�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GjsPrivate from 'gi://GjsPrivate'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Meta from 'gi://Meta'; /* * DBus Interface Info */ const DBUS_NAME = 'org.gnome.Shell.Extensions.GSConnect.Clipboard'; const DBUS_PATH = '/org/gnome/Shell/Extensions/GSConnect/Clipboard'; const DBUS_NODE = Gio.DBusNodeInfo.new_for_xml(` <node> <interface name="org.gnome.Shell.Extensions.GSConnect.Clipboard"> <!-- Methods --> <method name="GetMimetypes"> <arg direction="out" type="as" name="mimetypes"/> </method> <method name="GetText"> <arg direction="out" type="s" name="text"/> </method> <method name="SetText"> <arg direction="in" type="s" name="text"/> </method> <method name="GetValue"> <arg direction="in" type="s" name="mimetype"/> <arg direction="out" type="ay" name="value"/> </method> <method name="SetValue"> <arg direction="in" type="ay" name="value"/> <arg direction="in" type="s" name="mimetype"/> </method> <!-- Signals --> <signal name="OwnerChange"/> </interface> </node> `); const DBUS_INFO = DBUS_NODE.lookup_interface(DBUS_NAME); /* * Text Mimetypes */ const TEXT_MIMETYPES = [ 'text/plain;charset=utf-8', 'UTF8_STRING', 'text/plain', 'STRING', ]; /* GSConnectClipboardPortal: * * A simple clipboard portal, especially useful on Wayland where GtkClipboard * doesn't work in the background. */ export const Clipboard = GObject.registerClass({ GTypeName: 'GSConnectShellClipboard', }, class GSConnectShellClipboard extends GjsPrivate.DBusImplementation { _init(params = {}) { super._init({ g_interface_info: DBUS_INFO, }); this._transferring = false; // Watch global selection this._selection = global.display.get_selection(); this._ownerChangedId = this._selection.connect( 'owner-changed', this._onOwnerChanged.bind(this) ); // Prepare DBus interface this._handleMethodCallId = this.connect( 'handle-method-call', this._onHandleMethodCall.bind(this) ); this._nameId = Gio.DBus.own_name( Gio.BusType.SESSION, DBUS_NAME, Gio.BusNameOwnerFlags.NONE, this._onBusAcquired.bind(this), null, this._onNameLost.bind(this) ); } _onOwnerChanged(selection, type, source) { /* We're only interested in the standard clipboard */ if (type !== Meta.SelectionType.SELECTION_CLIPBOARD) return; /* In Wayland an intermediate GMemoryOutputStream is used which triggers * a second ::owner-changed emission, so we need to ensure we ignore * that while the transfer is resolving. */ if (this._transferring) return; this._transferring = true; /* We need to put our signal emission in an idle callback to ensure that * Mutter's internal calls have finished resolving in the loop, or else * we'll end up with the previous selection's content. */ GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { this.emit_signal('OwnerChange', null); this._transferring = false; return GLib.SOURCE_REMOVE; }); } _onBusAcquired(connection, name) { try { this.export(connection, DBUS_PATH); } catch (e) { logError(e); } } _onNameLost(connection, name) { try { this.unexport(); } catch (e) { logError(e); } } async _onHandleMethodCall(iface, name, parameters, invocation) { let retval; try { const args = parameters.recursiveUnpack(); retval = await this[name](...args); } catch (e) { if (e instanceof GLib.Error) { invocation.return_gerror(e); } else { if (!e.name.includes('.')) e.name = `org.gnome.gjs.JSError.${e.name}`; invocation.return_dbus_error(e.name, e.message); } return; } if (retval === undefined) retval = new GLib.Variant('()', []); try { if (!(retval instanceof GLib.Variant)) { const args = DBUS_INFO.lookup_method(name).out_args; retval = new GLib.Variant( `(${args.map(arg => arg.signature).join('')})`, (args.length === 1) ? [retval] : retval ); } invocation.return_value(retval); // Without a response, the client will wait for timeout } catch (e) { invocation.return_dbus_error( 'org.gnome.gjs.JSError.ValueError', 'Service implementation returned an incorrect value type' ); } } /** * Get the available mimetypes of the current clipboard content * * @return {Promise<string[]>} A list of mime-types */ GetMimetypes() { return new Promise((resolve, reject) => { try { const mimetypes = this._selection.get_mimetypes( Meta.SelectionType.SELECTION_CLIPBOARD ); resolve(mimetypes); } catch (e) { reject(e); } }); } /** * Get the text content of the clipboard * * @return {Promise<string>} Text content of the clipboard */ GetText() { return new Promise((resolve, reject) => { const mimetypes = this._selection.get_mimetypes( Meta.SelectionType.SELECTION_CLIPBOARD); const mimetype = TEXT_MIMETYPES.find(type => mimetypes.includes(type)); if (mimetype !== undefined) { const stream = Gio.MemoryOutputStream.new_resizable(); this._selection.transfer_async( Meta.SelectionType.SELECTION_CLIPBOARD, mimetype, -1, stream, null, (selection, res) => { try { selection.transfer_finish(res); const bytes = stream.steal_as_bytes(); const bytearray = bytes.get_data(); resolve(new TextDecoder().decode(bytearray)); } catch (e) { reject(e); } } ); } else { reject(new Error('text not available')); } }); } /** * Set the text content of the clipboard * * @param {string} text - text content to set * @return {Promise} A promise for the operation */ SetText(text) { return new Promise((resolve, reject) => { try { if (typeof text !== 'string') { throw new Gio.DBusError({ code: Gio.DBusError.INVALID_ARGS, message: 'expected string', }); } const source = Meta.SelectionSourceMemory.new( 'text/plain;charset=utf-8', GLib.Bytes.new(text)); this._selection.set_owner( Meta.SelectionType.SELECTION_CLIPBOARD, source); resolve(); } catch (e) { reject(e); } }); } /** * Get the content of the clipboard with the type @mimetype. * * @param {string} mimetype - the mimetype to request * @return {Promise<Uint8Array>} The content of the clipboard */ GetValue(mimetype) { return new Promise((resolve, reject) => { const stream = Gio.MemoryOutputStream.new_resizable(); this._selection.transfer_async( Meta.SelectionType.SELECTION_CLIPBOARD, mimetype, -1, stream, null, (selection, res) => { try { selection.transfer_finish(res); const bytes = stream.steal_as_bytes(); resolve(bytes.get_data()); } catch (e) { reject(e); } } ); }); } /** * Set the content of the clipboard to @value with the type @mimetype. * * @param {Uint8Array} value - the value to set * @param {string} mimetype - the mimetype of the value * @return {Promise} - A promise for the operation */ SetValue(value, mimetype) { return new Promise((resolve, reject) => { try { const source = Meta.SelectionSourceMemory.new(mimetype, GLib.Bytes.new(value)); this._selection.set_owner( Meta.SelectionType.SELECTION_CLIPBOARD, source); resolve(); } catch (e) { reject(e); } }); } destroy() { if (this._selection && this._ownerChangedId > 0) { this._selection.disconnect(this._ownerChangedId); this._ownerChangedId = 0; } if (this._nameId > 0) { Gio.bus_unown_name(this._nameId); this._nameId = 0; } if (this._handleMethodCallId > 0) { this.disconnect(this._handleMethodCallId); this._handleMethodCallId = 0; this.unexport(); } } }); let _portal = null; let _portalId = 0; /** * Watch for the service to start and export the clipboard portal when it does. */ export function watchService() { if (GLib.getenv('XDG_SESSION_TYPE') !== 'wayland') return; if (_portalId > 0) return; _portalId = Gio.bus_watch_name( Gio.BusType.SESSION, 'org.gnome.Shell.Extensions.GSConnect', Gio.BusNameWatcherFlags.NONE, () => { if (_portal === null) _portal = new Clipboard(); }, () => { if (_portal !== null) { _portal.destroy(); _portal = null; } } ); } /** * Stop watching the service and export the portal if currently running. */ export function unwatchService() { if (_portalId > 0) { Gio.bus_unwatch_name(_portalId); _portalId = 0; } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/device.js�������������������������������0000664�0000000�0000000�00000026001�14607666711�0025536�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Clutter from 'gi://Clutter'; import GObject from 'gi://GObject'; import St from 'gi://St'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; import {getIcon} from './utils.js'; import * as GMenu from './gmenu.js'; import Tooltip from './tooltip.js'; /** * A battery widget with an icon, text percentage and time estimate tooltip */ export const Battery = GObject.registerClass({ GTypeName: 'GSConnectShellDeviceBattery', }, class Battery extends St.BoxLayout { _init(params) { super._init({ reactive: true, style_class: 'gsconnect-device-battery', track_hover: true, }); Object.assign(this, params); // Percent Label this.label = new St.Label({ y_align: Clutter.ActorAlign.CENTER, }); this.label.clutter_text.ellipsize = 0; this.add_child(this.label); // Battery Icon this.icon = new St.Icon({ fallback_icon_name: 'battery-missing-symbolic', icon_size: 16, }); this.add_child(this.icon); // Battery Estimate this.tooltip = new Tooltip({ parent: this, text: null, }); // Battery GAction this._actionAddedId = this.device.action_group.connect( 'action-added', this._onActionChanged.bind(this) ); this._actionRemovedId = this.device.action_group.connect( 'action-removed', this._onActionChanged.bind(this) ); this._actionStateChangedId = this.device.action_group.connect( 'action-state-changed', this._onStateChanged.bind(this) ); this._onActionChanged(this.device.action_group, 'battery'); // Cleanup on destroy this.connect('destroy', this._onDestroy); } _onActionChanged(action_group, action_name) { if (action_name !== 'battery') return; if (action_group.has_action('battery')) { const value = action_group.get_action_state('battery'); const [charging, icon_name, level, time] = value.deepUnpack(); this._state = { charging: charging, icon_name: icon_name, level: level, time: time, }; } else { this._state = null; } this._sync(); } _onStateChanged(action_group, action_name, value) { if (action_name !== 'battery') return; const [charging, icon_name, level, time] = value.deepUnpack(); this._state = { charging: charging, icon_name: icon_name, level: level, time: time, }; this._sync(); } _getBatteryLabel() { if (!this._state) return null; const {charging, level, time} = this._state; if (level === 100) // TRANSLATORS: When the battery level is 100% return _('Fully Charged'); if (time === 0) // TRANSLATORS: When no time estimate for the battery is available // EXAMPLE: 42% (Estimating…) return _('%d%% (Estimating…)').format(level); const total = time / 60; const minutes = Math.floor(total % 60); const hours = Math.floor(total / 60); if (charging) { // TRANSLATORS: Estimated time until battery is charged // EXAMPLE: 42% (1:15 Until Full) return _('%d%% (%d\u2236%02d Until Full)').format( level, hours, minutes ); } else { // TRANSLATORS: Estimated time until battery is empty // EXAMPLE: 42% (12:15 Remaining) return _('%d%% (%d\u2236%02d Remaining)').format( level, hours, minutes ); } } _onDestroy(actor) { actor.device.action_group.disconnect(actor._actionAddedId); actor.device.action_group.disconnect(actor._actionRemovedId); actor.device.action_group.disconnect(actor._actionStateChangedId); } _sync() { this.visible = !!this._state; if (!this.visible) return; this.icon.icon_name = this._state.icon_name; this.label.text = (this._state.level > -1) ? `${this._state.level}%` : ''; this.tooltip.text = this._getBatteryLabel(); } }); /** * A cell signal strength widget with two icons */ export const SignalStrength = GObject.registerClass({ GTypeName: 'GSConnectShellDeviceSignalStrength', }, class SignalStrength extends St.BoxLayout { _init(params) { super._init({ reactive: true, style_class: 'gsconnect-device-signal-strength', track_hover: true, }); Object.assign(this, params); // Network Type Icon this.networkTypeIcon = new St.Icon({ fallback_icon_name: 'network-cellular-symbolic', icon_size: 16, }); this.add_child(this.networkTypeIcon); // Signal Strength Icon this.signalStrengthIcon = new St.Icon({ fallback_icon_name: 'network-cellular-offline-symbolic', icon_size: 16, }); this.add_child(this.signalStrengthIcon); // Network Type Text this.tooltip = new Tooltip({ parent: this, text: null, }); // ConnectivityReport GAction this._actionAddedId = this.device.action_group.connect( 'action-added', this._onActionChanged.bind(this) ); this._actionRemovedId = this.device.action_group.connect( 'action-removed', this._onActionChanged.bind(this) ); this._actionStateChangedId = this.device.action_group.connect( 'action-state-changed', this._onStateChanged.bind(this) ); this._onActionChanged(this.device.action_group, 'connectivityReport'); // Cleanup on destroy this.connect('destroy', this._onDestroy); } _onActionChanged(action_group, action_name) { if (action_name !== 'connectivityReport') return; if (action_group.has_action('connectivityReport')) { const value = action_group.get_action_state('connectivityReport'); const [ cellular_network_type, cellular_network_type_icon, cellular_network_strength, cellular_network_strength_icon, hotspot_name, hotspot_bssid, ] = value.deepUnpack(); this._state = { cellular_network_type: cellular_network_type, cellular_network_type_icon: cellular_network_type_icon, cellular_network_strength: cellular_network_strength, cellular_network_strength_icon: cellular_network_strength_icon, hotspot_name: hotspot_name, hotspot_bssid: hotspot_bssid, }; } else { this._state = null; } this._sync(); } _onStateChanged(action_group, action_name, value) { if (action_name !== 'connectivityReport') return; const [ cellular_network_type, cellular_network_type_icon, cellular_network_strength, cellular_network_strength_icon, hotspot_name, hotspot_bssid, ] = value.deepUnpack(); this._state = { cellular_network_type: cellular_network_type, cellular_network_type_icon: cellular_network_type_icon, cellular_network_strength: cellular_network_strength, cellular_network_strength_icon: cellular_network_strength_icon, hotspot_name: hotspot_name, hotspot_bssid: hotspot_bssid, }; this._sync(); } _onDestroy(actor) { actor.device.action_group.disconnect(actor._actionAddedId); actor.device.action_group.disconnect(actor._actionRemovedId); actor.device.action_group.disconnect(actor._actionStateChangedId); } _sync() { this.visible = !!this._state; if (!this.visible) return; this.networkTypeIcon.icon_name = this._state.cellular_network_type_icon; this.signalStrengthIcon.icon_name = this._state.cellular_network_strength_icon; this.tooltip.text = this._state.cellular_network_type; } }); /** * A PopupMenu used as an information and control center for a device */ export class Menu extends PopupMenu.PopupMenuSection { constructor(params) { super(); Object.assign(this, params); this.actor.add_style_class_name('gsconnect-device-menu'); // Title this._title = new PopupMenu.PopupSeparatorMenuItem(this.device.name); this.addMenuItem(this._title); // Title -> Name this._title.label.style_class = 'gsconnect-device-name'; this._title.label.clutter_text.ellipsize = 0; this.device.bind_property( 'name', this._title.label, 'text', GObject.BindingFlags.SYNC_CREATE ); // Title -> Cellular Signal Strength this._signalStrength = new SignalStrength({device: this.device}); this._title.actor.add_child(this._signalStrength); // Title -> Battery this._battery = new Battery({device: this.device}); this._title.actor.add_child(this._battery); // Actions let actions; if (this.menu_type === 'icon') { actions = new GMenu.IconBox({ action_group: this.device.action_group, model: this.device.menu, }); } else if (this.menu_type === 'list') { actions = new GMenu.ListBox({ action_group: this.device.action_group, model: this.device.menu, }); } this.addMenuItem(actions); } isEmpty() { return false; } } /** * An indicator representing a Device in the Status Area */ export const Indicator = GObject.registerClass({ GTypeName: 'GSConnectDeviceIndicator', }, class Indicator extends PanelMenu.Button { _init(params) { super._init(0.0, `${params.device.name} Indicator`, false); Object.assign(this, params); // Device Icon this._icon = new St.Icon({ gicon: getIcon(this.device.icon_name), style_class: 'system-status-icon gsconnect-device-indicator', }); this.add_child(this._icon); // Menu const menu = new Menu({ device: this.device, menu_type: 'icon', }); this.menu.addMenuItem(menu); } }); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/gmenu.js��������������������������������0000664�0000000�0000000�00000044516�14607666711�0025425�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Atk from 'gi://Atk'; import Clutter from 'gi://Clutter'; import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import St from 'gi://St'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {getIcon} from './utils.js'; import Tooltip from './tooltip.js'; /** * Get a dictionary of a GMenuItem's attributes * * @param {Gio.MenuModel} model - The menu model containing the item * @param {number} index - The index of the item in @model * @return {Object} A dictionary of the item's attributes */ function getItemInfo(model, index) { const info = { target: null, links: [], }; // let iter = model.iterate_item_attributes(index); while (iter.next()) { const name = iter.get_name(); let value = iter.get_value(); switch (name) { case 'icon': value = Gio.Icon.deserialize(value); if (value instanceof Gio.ThemedIcon) value = getIcon(value.names[0]); info[name] = value; break; case 'target': info[name] = value; break; default: info[name] = value.unpack(); } } // Submenus & Sections iter = model.iterate_item_links(index); while (iter.next()) { info.links.push({ name: iter.get_name(), value: iter.get_value(), }); } return info; } /** * */ export class ListBox extends PopupMenu.PopupMenuSection { constructor(params) { super(); Object.assign(this, params); // Main Actor this.actor = new St.BoxLayout({ x_expand: true, clip_to_allocation: true, }); this.actor._delegate = this; // Item Box this.box.clip_to_allocation = true; this.box.x_expand = true; this.box.add_style_class_name('gsconnect-list-box'); this.box.set_pivot_point(1, 1); this.actor.add_child(this.box); // Submenu Container this.sub = new St.BoxLayout({ clip_to_allocation: true, vertical: false, visible: false, x_expand: true, }); this.sub.set_pivot_point(1, 1); this.sub._delegate = this; this.actor.add_child(this.sub); // Handle transitions this._boxTransitionsCompletedId = this.box.connect( 'transitions-completed', this._onTransitionsCompleted.bind(this) ); this._subTransitionsCompletedId = this.sub.connect( 'transitions-completed', this._onTransitionsCompleted.bind(this) ); // Handle keyboard navigation this._submenuCloseKeyId = this.sub.connect( 'key-press-event', this._onSubmenuCloseKey.bind(this) ); // Refresh the menu when mapped this._mappedId = this.actor.connect( 'notify::mapped', this._onMapped.bind(this) ); // Watch the model for changes this._itemsChangedId = this.model.connect( 'items-changed', this._onItemsChanged.bind(this) ); this._onItemsChanged(); } _onMapped(actor) { if (actor.mapped) { this._onItemsChanged(); // We use this instead of close() to avoid touching finalized objects } else { this.box.set_opacity(255); this.box.set_width(-1); this.box.set_height(-1); this.box.visible = true; this._submenu = null; this.sub.set_opacity(0); this.sub.set_width(0); this.sub.set_height(0); this.sub.visible = false; this.sub.get_children().map(menu => menu.hide()); } } _onSubmenuCloseKey(actor, event) { if (this.submenu && event.get_key_symbol() === Clutter.KEY_Left) { this.submenu.submenu_for.setActive(true); this.submenu = null; return Clutter.EVENT_STOP; } return Clutter.EVENT_PROPAGATE; } _onSubmenuOpenKey(actor, event) { const item = actor._delegate; if (item.submenu && event.get_key_symbol() === Clutter.KEY_Right) { this.submenu = item.submenu; item.submenu.firstMenuItem.setActive(true); } return Clutter.EVENT_PROPAGATE; } _onGMenuItemActivate(item, event) { this.emit('activate', item); if (item.submenu) { this.submenu = item.submenu; } else if (item.action_name) { this.action_group.activate_action( item.action_name, item.action_target ); this.itemActivated(); } } _addGMenuItem(info) { const item = new PopupMenu.PopupMenuItem(info.label); this.addMenuItem(item); if (info.action !== undefined) { item.action_name = info.action.split('.')[1]; item.action_target = info.target; item.actor.visible = this.action_group.get_action_enabled( item.action_name ); } item.connectObject( 'activate', this._onGMenuItemActivate.bind(this), this ); return item; } _addGMenuSection(model) { const section = new ListBox({ model: model, action_group: this.action_group, }); this.addMenuItem(section); } _addGMenuSubmenu(model, item) { // Add an expander arrow to the item const arrow = PopupMenu.arrowIcon(St.Side.RIGHT); arrow.x_align = Clutter.ActorAlign.END; arrow.x_expand = true; item.actor.add_child(arrow); // Mark it as an expandable and open on right-arrow item.actor.add_accessible_state(Atk.StateType.EXPANDABLE); item.actor.connect( 'key-press-event', this._onSubmenuOpenKey.bind(this) ); // Create the submenu item.submenu = new ListBox({ model: model, action_group: this.action_group, submenu_for: item, _parent: this, }); item.submenu.actor.hide(); // Add to the submenu container this.sub.add_child(item.submenu.actor); } _onItemsChanged(model, position, removed, added) { // Clear the menu this.removeAll(); this.sub.get_children().map(child => child.destroy()); for (let i = 0, len = this.model.get_n_items(); i < len; i++) { const info = getItemInfo(this.model, i); let item; // A regular item if (info.hasOwnProperty('label')) item = this._addGMenuItem(info); for (const link of info.links) { // Submenu if (link.name === 'submenu') { this._addGMenuSubmenu(link.value, item); // Section } else if (link.name === 'section') { this._addGMenuSection(link.value); // len is length starting at 1 if (i + 1 < len) this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); } } } // If this is a submenu of another item... if (this.submenu_for) { // Prepend an "<= Go Back" item, bold with a unicode arrow const prev = new PopupMenu.PopupMenuItem(this.submenu_for.label.text); prev.label.style = 'font-weight: bold;'; const prevArrow = PopupMenu.arrowIcon(St.Side.LEFT); prev.replace_child(prev._ornamentIcon, prevArrow); this.addMenuItem(prev, 0); prev.connectObject('activate', (item, event) => { this.emit('activate', item); this._parent.submenu = null; }, this); } } _onTransitionsCompleted(actor) { if (this.submenu) { this.box.visible = false; } else { this.sub.visible = false; this.sub.get_children().map(menu => menu.hide()); } } get submenu() { return this._submenu || null; } set submenu(submenu) { // Get the current allocation to hold the menu width const allocation = this.actor.allocation; const width = Math.max(0, allocation.x2 - allocation.x1); // Prepare the appropriate child for tweening if (submenu) { this.sub.set_opacity(0); this.sub.set_width(0); this.sub.set_height(0); this.sub.visible = true; } else { this.box.set_opacity(0); this.box.set_width(0); this.sub.set_height(0); this.box.visible = true; } // Setup the animation this.box.save_easing_state(); this.box.set_easing_mode(Clutter.AnimationMode.EASE_IN_OUT_CUBIC); this.box.set_easing_duration(250); this.sub.save_easing_state(); this.sub.set_easing_mode(Clutter.AnimationMode.EASE_IN_OUT_CUBIC); this.sub.set_easing_duration(250); if (submenu) { submenu.actor.show(); this.sub.set_opacity(255); this.sub.set_width(width); this.sub.set_height(-1); this.box.set_opacity(0); this.box.set_width(0); this.box.set_height(0); } else { this.box.set_opacity(255); this.box.set_width(width); this.box.set_height(-1); this.sub.set_opacity(0); this.sub.set_width(0); this.sub.set_height(0); } // Reset the animation this.box.restore_easing_state(); this.sub.restore_easing_state(); // this._submenu = submenu; } destroy() { this.actor.disconnect(this._mappedId); this.box.disconnect(this._boxTransitionsCompletedId); this.sub.disconnect(this._subTransitionsCompletedId); this.sub.disconnect(this._submenuCloseKeyId); this.model.disconnect(this._itemsChangedId); super.destroy(); } } /** * A St.Button subclass for iconic GMenu items */ export const IconButton = GObject.registerClass({ GTypeName: 'GSConnectShellIconButton', }, class Button extends St.Button { _init(params) { super._init({ style_class: 'gsconnect-icon-button', can_focus: true, }); Object.assign(this, params); // Item attributes if (params.info.hasOwnProperty('action')) this.action_name = params.info.action.split('.')[1]; if (params.info.hasOwnProperty('target')) this.action_target = params.info.target; if (params.info.hasOwnProperty('label')) { this.tooltip = new Tooltip({ parent: this, markup: params.info.label, }); this.accessible_name = params.info.label; } if (params.info.hasOwnProperty('icon')) this.child = new St.Icon({gicon: params.info.icon}); // Submenu for (const link of params.info.links) { if (link.name === 'submenu') { this.add_accessible_state(Atk.StateType.EXPANDABLE); this.toggle_mode = true; this.connect('notify::checked', this._onChecked); this.submenu = new ListBox({ model: link.value, action_group: this.action_group, _parent: this._parent, }); this.submenu.actor.style_class = 'popup-sub-menu'; this.submenu.actor.visible = false; } } } // This is (reliably?) emitted before ::clicked _onChecked(button) { if (button.checked) { button.add_accessible_state(Atk.StateType.EXPANDED); button.add_style_pseudo_class('active'); } else { button.remove_accessible_state(Atk.StateType.EXPANDED); button.remove_style_pseudo_class('active'); } } // This is (reliably?) emitted after notify::checked vfunc_clicked(clicked_button) { // Unless this has a submenu, activate the action and close the menu if (!this.toggle_mode) { this._parent._getTopMenu().close(); this.action_group.activate_action( this.action_name, this.action_target ); // StButton.checked has already been toggled so we're opening } else if (this.checked) { this._parent.submenu = this.submenu; // If this is the active submenu being closed, animate-close it } else if (this._parent.submenu === this.submenu) { this._parent.submenu = null; } } }); export class IconBox extends PopupMenu.PopupMenuSection { constructor(params) { super(); Object.assign(this, params); // Main Actor this.actor = new St.BoxLayout({ vertical: true, x_expand: true, }); this.actor._delegate = this; // Button Box this.box._delegate = this; this.box.style_class = 'gsconnect-icon-box'; this.box.vertical = false; this.actor.add_child(this.box); // Submenu Container this.sub = new St.BoxLayout({ clip_to_allocation: true, vertical: true, x_expand: true, }); this.sub.connect('transitions-completed', this._onTransitionsCompleted); this.sub._delegate = this; this.actor.add_child(this.sub); // Track menu items so we can use ::items-changed this._menu_items = new Map(); // PopupMenu this._mappedId = this.actor.connect( 'notify::mapped', this._onMapped.bind(this) ); // GMenu this._itemsChangedId = this.model.connect( 'items-changed', this._onItemsChanged.bind(this) ); // GActions this._actionAddedId = this.action_group.connect( 'action-added', this._onActionChanged.bind(this) ); this._actionEnabledChangedId = this.action_group.connect( 'action-enabled-changed', this._onActionChanged.bind(this) ); this._actionRemovedId = this.action_group.connect( 'action-removed', this._onActionChanged.bind(this) ); } destroy() { this.actor.disconnect(this._mappedId); this.model.disconnect(this._itemsChangedId); this.action_group.disconnect(this._actionAddedId); this.action_group.disconnect(this._actionEnabledChangedId); this.action_group.disconnect(this._actionRemovedId); super.destroy(); } get submenu() { return this._submenu || null; } set submenu(submenu) { if (submenu) { for (const button of this.box.get_children()) { if (button.submenu && this._submenu && button.submenu !== submenu) { button.checked = false; button.submenu.actor.hide(); } } this.sub.set_height(0); submenu.actor.show(); } this.sub.save_easing_state(); this.sub.set_easing_duration(250); this.sub.set_easing_mode(Clutter.AnimationMode.EASE_IN_OUT_CUBIC); this.sub.set_height(submenu ? submenu.actor.get_preferred_size()[1] : 0); this.sub.restore_easing_state(); this._submenu = submenu; } _onMapped(actor) { if (!actor.mapped) { this._submenu = null; for (const button of this.box.get_children()) button.checked = false; for (const submenu of this.sub.get_children()) submenu.hide(); } } _onActionChanged(group, name, enabled) { const menuItem = this._menu_items.get(name); if (menuItem !== undefined) menuItem.visible = group.get_action_enabled(name); } _onItemsChanged(model, position, removed, added) { // Remove items while (removed > 0) { const button = this.box.get_child_at_index(position); const action_name = button.action_name; if (button.submenu) button.submenu.destroy(); button.destroy(); this._menu_items.delete(action_name); removed--; } // Add items for (let i = 0; i < added; i++) { const index = position + i; // Create an iconic button const button = new IconButton({ action_group: this.action_group, info: getItemInfo(model, index), // NOTE: Because this doesn't derive from a PopupMenu class // it lacks some things its parent will expect from it _parent: this, _delegate: null, }); // Set the visibility based on the enabled state if (button.action_name !== undefined) { button.visible = this.action_group.get_action_enabled( button.action_name ); } // If it has a submenu, add it as a sibling if (button.submenu) this.sub.add_child(button.submenu.actor); // Track the item if it has an action if (button.action_name !== undefined) this._menu_items.set(button.action_name, button); // Insert it in the box at the defined position this.box.insert_child_at_index(button, index); } } _onTransitionsCompleted(actor) { const menu = actor._delegate; for (const button of menu.box.get_children()) { if (button.submenu && button.submenu !== menu.submenu) { button.checked = false; button.submenu.actor.hide(); } } menu.sub.set_height(-1); } // PopupMenu.PopupMenuBase overrides isEmpty() { return (this.box.get_children().length === 0); } _setParent(parent) { super._setParent(parent); this._onItemsChanged(this.model, 0, 0, this.model.get_n_items()); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/input.js��������������������������������0000664�0000000�0000000�00000002124�14607666711�0025436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import Config from '../config.js'; export class LockscreenRemoteAccess { constructor() { this._inhibitor = null; this._settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect', null ), path: '/org/gnome/shell/extensions/gsconnect/', }); } patchInhibitor() { if (this._inhibitor) return; if (this._settings.get_boolean('keep-alive-when-locked')) { this._inhibitor = global.backend.get_remote_access_controller().inhibit_remote_access; global.backend.get_remote_access_controller().inhibit_remote_access = () => {}; } } unpatchInhibitor() { if (!this._inhibitor) return; global.backend.get_remote_access_controller().inhibit_remote_access = this._inhibitor; this._inhibitor = null; } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/keybindings.js��������������������������0000664�0000000�0000000�00000006247�14607666711�0026617�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import Meta from 'gi://Meta'; import Shell from 'gi://Shell'; /** * Keybindings.Manager is a simple convenience class for managing keyboard * shortcuts in GNOME Shell. You bind a shortcut using add(), which on success * will return a non-zero action id that can later be used with remove() to * unbind the shortcut. * * Accelerators are accepted in the form returned by Gtk.accelerator_name() and * callbacks are invoked directly, so should be complete closures. * * References: * https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html * https://developer.gnome.org/meta/stable/MetaDisplay.html * https://developer.gnome.org/meta/stable/meta-MetaKeybinding.html * https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/js/ui/windowManager.js#L1093-1112 */ export class Manager { constructor() { this._keybindings = new Map(); this._acceleratorActivatedId = global.display.connect( 'accelerator-activated', this._onAcceleratorActivated.bind(this) ); } _onAcceleratorActivated(display, action, inputDevice, timestamp) { try { const binding = this._keybindings.get(action); if (binding !== undefined) binding.callback(); } catch (e) { logError(e); } } /** * Add a keybinding with callback * * @param {string} accelerator - An accelerator in the form '<Control>q' * @param {Function} callback - A callback for the accelerator * @return {number} A non-zero action id on success, or 0 on failure */ add(accelerator, callback) { try { const action = global.display.grab_accelerator(accelerator, 0); if (action === Meta.KeyBindingAction.NONE) throw new Error(`Failed to add keybinding: '${accelerator}'`); const name = Meta.external_binding_name_for_action(action); Main.wm.allowKeybinding(name, Shell.ActionMode.ALL); this._keybindings.set(action, {name: name, callback: callback}); return action; } catch (e) { logError(e); } } /** * Remove a keybinding * * @param {number} action - A non-zero action id returned by add() */ remove(action) { try { const binding = this._keybindings.get(action); global.display.ungrab_accelerator(action); Main.wm.allowKeybinding(binding.name, Shell.ActionMode.NONE); this._keybindings.delete(action); } catch (e) { logError(new Error(`Failed to remove keybinding: ${e.message}`)); } } /** * Remove all keybindings */ removeAll() { for (const action of this._keybindings.keys()) this.remove(action); } /** * Destroy the keybinding manager and remove all keybindings */ destroy() { global.display.disconnect(this._acceleratorActivatedId); this.removeAll(); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/notification.js�������������������������0000664�0000000�0000000�00000035253�14607666711�0026776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import St from 'gi://St'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'; import * as Calendar from 'resource:///org/gnome/shell/ui/calendar.js'; import * as NotificationDaemon from 'resource:///org/gnome/shell/ui/notificationDaemon.js'; import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; import {getIcon} from './utils.js'; const APP_ID = 'org.gnome.Shell.Extensions.GSConnect'; const APP_PATH = '/org/gnome/Shell/Extensions/GSConnect'; // deviceId Pattern (<device-id>|<remote-id>) const DEVICE_REGEX = new RegExp(/^([^|]+)\|([\s\S]+)$/); // requestReplyId Pattern (<device-id>|<remote-id>)|<reply-id>) const REPLY_REGEX = new RegExp(/^([^|]+)\|([\s\S]+)\|([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/, 'i'); /** * Extracted from notificationDaemon.js, as it's no longer exported * https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/notificationDaemon.js#L556 * @returns {{ 'desktop-startup-id': string }} Object with ID containing current time */ function getPlatformData() { const startupId = GLib.Variant.new('s', `_TIME${global.get_current_time()}`); return {'desktop-startup-id': startupId}; } // This is no longer directly exported, so we do this instead for now const GtkNotificationDaemon = Main.notificationDaemon._gtkNotificationDaemon.constructor; /** * A slightly modified Notification Banner with an entry field */ const NotificationBanner = GObject.registerClass({ GTypeName: 'GSConnectNotificationBanner', }, class NotificationBanner extends Calendar.NotificationMessage { constructor(notification) { super(notification); if (notification.requestReplyId !== undefined) this._addReplyAction(); } _addReplyAction() { if (!this._buttonBox) { this._buttonBox = new St.BoxLayout({ style_class: 'notification-buttons-bin', x_expand: true, }); this.setActionArea(this._buttonBox); global.focus_manager.add_group(this._buttonBox); } // Reply Button const button = new St.Button({ style_class: 'notification-button', label: _('Reply'), x_expand: true, can_focus: true, }); button.connect( 'clicked', this._onEntryRequested.bind(this) ); this._buttonBox.add_child(button); // Reply Entry this._replyEntry = new St.Entry({ can_focus: true, hint_text: _('Type a message'), style_class: 'chat-response', x_expand: true, visible: false, }); this._buttonBox.add_child(this._replyEntry); } _onEntryRequested(button) { this.focused = true; for (const child of this._buttonBox.get_children()) child.visible = (child === this._replyEntry); // Release the notification focus with the entry focus this._replyEntry.connect( 'key-focus-out', this._onEntryDismissed.bind(this) ); this._replyEntry.clutter_text.connect( 'activate', this._onEntryActivated.bind(this) ); this._replyEntry.grab_key_focus(); } _onEntryDismissed(entry) { this.focused = false; this.emit('unfocused'); } _onEntryActivated(clutter_text) { // Refuse to send empty replies if (clutter_text.text === '') return; // Copy the text, then clear the entry const text = clutter_text.text; clutter_text.text = ''; const {deviceId, requestReplyId} = this.notification; const target = new GLib.Variant('(ssbv)', [ deviceId, 'replyNotification', true, new GLib.Variant('(ssa{ss})', [requestReplyId, text, {}]), ]); const platformData = getPlatformData(); Gio.DBus.session.call( APP_ID, APP_PATH, 'org.freedesktop.Application', 'ActivateAction', GLib.Variant.new('(sava{sv})', ['device', [target], platformData]), null, Gio.DBusCallFlags.NO_AUTO_START, -1, null, (connection, res) => { try { connection.call_finish(res); } catch (e) { // Silence errors } } ); this.close(); } }); /** * A custom notification source for spawning notifications and closing device * notifications. This source isn't actually used, but it's methods are patched * into existing sources. */ const Source = GObject.registerClass({ GTypeName: 'GSConnectNotificationSource', }, class Source extends NotificationDaemon.GtkNotificationDaemonAppSource { _closeGSConnectNotification(notification, reason) { if (reason !== MessageTray.NotificationDestroyedReason.DISMISSED) return; // Avoid sending the request multiple times if (notification._remoteClosed || notification.remoteId === undefined) return; notification._remoteClosed = true; const target = new GLib.Variant('(ssbv)', [ notification.deviceId, 'closeNotification', true, new GLib.Variant('s', notification.remoteId), ]); const platformData = getPlatformData(); Gio.DBus.session.call( APP_ID, APP_PATH, 'org.freedesktop.Application', 'ActivateAction', GLib.Variant.new('(sava{sv})', ['device', [target], platformData]), null, Gio.DBusCallFlags.NO_AUTO_START, -1, null, (connection, res) => { try { connection.call_finish(res); } catch (e) { // If we fail, reset in case we can try again notification._remoteClosed = false; } } ); } /* * Parse the id to determine if it's a repliable notification, device * notification or a regular local notification */ _parseNotificationId(notificationId) { let idMatch, deviceId, requestReplyId, remoteId, localId; if ((idMatch = REPLY_REGEX.exec(notificationId))) { [, deviceId, remoteId, requestReplyId] = idMatch; localId = `${deviceId}|${remoteId}`; } else if ((idMatch = DEVICE_REGEX.exec(notificationId))) { [, deviceId, remoteId] = idMatch; localId = `${deviceId}|${remoteId}`; } else { localId = notificationId; } return [idMatch, deviceId, requestReplyId, remoteId, localId]; } /* * Add notification to source or update existing notification with extra * GsConnect information */ _createNotification(notification) { const [idMatch, deviceId, requestReplyId, remoteId, localId] = this._parseNotificationId(notification.id); const cachedNotification = this._notifications[localId]; // Check if this is a repeat if (cachedNotification) { cachedNotification.requestReplyId = requestReplyId; // Bail early If @notificationParams represents an exact repeat const title = notification.title; const body = notification.body ? notification.body : null; if (cachedNotification.title === title && cachedNotification.body === body) return cachedNotification; cachedNotification.title = title; cachedNotification.body = body; return cachedNotification; } // Device Notification if (idMatch) { notification.deviceId = deviceId; notification.remoteId = remoteId; notification.requestReplyId = requestReplyId; notification.connect('destroy', (notification, reason) => { this._closeGSConnectNotification(notification, reason); delete this._notifications[localId]; }); // Service Notification } else { notification.connect('destroy', (notification, reason) => { delete this._notifications[localId]; }); } this._notifications[localId] = notification; return notification; } /* * Override to control notification spawning */ addNotification(notification) { this._notificationPending = true; // Fix themed icons if (notification.icon) { let gicon = notification.icon; if (gicon instanceof Gio.ThemedIcon) { gicon = getIcon(gicon.names[0]); notification.icon = gicon.serialize(); } } const createdNotification = this._createNotification(notification); this._addNotificationToMessageTray(createdNotification); this._notificationPending = false; } /* * Reimplementation of MessageTray.addNotification to raise the usual * notification limit (3) */ _addNotificationToMessageTray(notification) { if (this.notifications.includes(notification)) { notification.acknowledged = false; return; } while (this.notifications.length >= 10) { const [oldest] = this.notifications; oldest.destroy(MessageTray.NotificationDestroyedReason.EXPIRED); } notification.connect('destroy', this._onNotificationDestroy.bind(this)); notification.connect('notify::acknowledged', () => { this.countUpdated(); // If acknowledged was set to false try to show the notification again if (!notification.acknowledged) this.emit('notification-request-banner', notification); }); this.notifications.push(notification); this.emit('notification-added', notification); this.emit('notification-request-banner', notification); } createBanner(notification) { return new NotificationBanner(notification); } }); /** * If there is an active GtkNotificationDaemonAppSource for GSConnect when the * extension is loaded, it has to be patched in place. */ export function patchGSConnectNotificationSource() { const source = Main.notificationDaemon._gtkNotificationDaemon._sources[APP_ID]; if (source !== undefined) { // Patch in the subclassed methods source._closeGSConnectNotification = Source.prototype._closeGSConnectNotification; source._parseNotificationId = Source.prototype._parseNotificationId; source._createNotification = Source.prototype._createNotification; source.addNotification = Source.prototype.addNotification; source._addNotificationToMessageTray = Source.prototype._addNotificationToMessageTray; source.createBanner = Source.prototype.createBanner; // Connect to existing notifications for (const notification of Object.values(source._notifications)) { const _id = notification.connect('destroy', (notification, reason) => { source._closeGSConnectNotification(notification, reason); notification.disconnect(_id); }); } } } /** * Wrap GtkNotificationDaemon._ensureAppSource() to patch GSConnect's app source * https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/js/ui/notificationDaemon.js#L742-755 */ const __ensureAppSource = GtkNotificationDaemon.prototype._ensureAppSource; // eslint-disable-next-line func-style const _ensureAppSource = function (appId) { const source = __ensureAppSource.call(this, appId); if (source._appId === APP_ID) { source._closeGSConnectNotification = Source.prototype._closeGSConnectNotification; source._parseNotificationId = Source.prototype._parseNotificationId; source._createNotification = Source.prototype._createNotification; source.addNotification = Source.prototype.addNotification; source._addNotificationToMessageTray = Source.prototype._addNotificationToMessageTray; source.createBanner = Source.prototype.createBanner; } return source; }; export function patchGtkNotificationDaemon() { GtkNotificationDaemon.prototype._ensureAppSource = _ensureAppSource; } export function unpatchGtkNotificationDaemon() { GtkNotificationDaemon.prototype._ensureAppSource = __ensureAppSource; } /** * We patch other Gtk notification sources so we can notify remote devices when * notifications have been closed locally. */ const _addNotification = NotificationDaemon.GtkNotificationDaemonAppSource.prototype.addNotification; export function patchGtkNotificationSources() { // eslint-disable-next-line func-style const _withdrawGSConnectNotification = function (id, notification, reason) { if (reason !== MessageTray.NotificationDestroyedReason.DISMISSED) return; // Avoid sending the request multiple times if (notification._remoteWithdrawn) return; notification._remoteWithdrawn = true; // Recreate the notification id as it would've been sent const target = new GLib.Variant('(ssbv)', [ '*', 'withdrawNotification', true, new GLib.Variant('s', `gtk|${this._appId}|${id}`), ]); const platformData = getPlatformData(); Gio.DBus.session.call( APP_ID, APP_PATH, 'org.freedesktop.Application', 'ActivateAction', GLib.Variant.new('(sava{sv})', ['device', [target], platformData]), null, Gio.DBusCallFlags.NO_AUTO_START, -1, null, (connection, res) => { try { connection.call_finish(res); } catch (e) { // If we fail, reset in case we can try again notification._remoteWithdrawn = false; } } ); }; NotificationDaemon.GtkNotificationDaemonAppSource.prototype._withdrawGSConnectNotification = _withdrawGSConnectNotification; } export function unpatchGtkNotificationSources() { NotificationDaemon.GtkNotificationDaemonAppSource.prototype.addNotification = _addNotification; delete NotificationDaemon.GtkNotificationDaemonAppSource.prototype._withdrawGSConnectNotification; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/tooltip.js������������������������������0000664�0000000�0000000�00000020004�14607666711�0025766�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Clutter from 'gi://Clutter'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Pango from 'gi://Pango'; import St from 'gi://St'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; /** * An StTooltip for ClutterActors * * Adapted from: https://github.com/RaphaelRochet/applications-overview-tooltip * See also: https://github.com/GNOME/gtk/blob/master/gtk/gtktooltip.c */ export let TOOLTIP_BROWSE_ID = 0; export let TOOLTIP_BROWSE_MODE = false; export default class Tooltip { constructor(params) { Object.assign(this, params); this._bin = null; this._hoverTimeoutId = 0; this._showing = false; this._destroyId = this.parent.connect( 'destroy', this.destroy.bind(this) ); this._hoverId = this.parent.connect( 'notify::hover', this._onHover.bind(this) ); this._buttonPressEventId = this.parent.connect( 'button-press-event', this._hide.bind(this) ); } get custom() { if (this._custom === undefined) this._custom = null; return this._custom; } set custom(actor) { this._custom = actor; this._markup = null; this._text = null; if (this._showing) this._show(); } get gicon() { if (this._gicon === undefined) this._gicon = null; return this._gicon; } set gicon(gicon) { this._gicon = gicon; if (this._showing) this._show(); } get icon() { return (this.gicon) ? this.gicon.name : null; } set icon(icon_name) { if (!icon_name) this.gicon = null; else this.gicon = new Gio.ThemedIcon({name: icon_name}); } get markup() { if (this._markup === undefined) this._markup = null; return this._markup; } set markup(text) { this._markup = text; this._text = null; if (this._showing) this._show(); } get text() { if (this._text === undefined) this._text = null; return this._text; } set text(text) { this._markup = null; this._text = text; if (this._showing) this._show(); } get x_offset() { if (this._x_offset === undefined) this._x_offset = 0; return this._x_offset; } set x_offset(offset) { this._x_offset = (Number.isInteger(offset)) ? offset : 0; } get y_offset() { if (this._y_offset === undefined) this._y_offset = 0; return this._y_offset; } set y_offset(offset) { this._y_offset = (Number.isInteger(offset)) ? offset : 0; } _show() { if (this.text === null && this.markup === null) return this._hide(); if (this._bin === null) { this._bin = new St.Bin({ style_class: 'osd-window gsconnect-tooltip', opacity: 232, }); if (this.custom) { this._bin.child = this.custom; } else { this._bin.child = new St.BoxLayout({vertical: false}); if (this.gicon) { this._bin.child.icon = new St.Icon({ gicon: this.gicon, y_align: St.Align.START, }); this._bin.child.icon.set_y_align(Clutter.ActorAlign.START); this._bin.child.add_child(this._bin.child.icon); } this.label = new St.Label({text: this.markup || this.text}); this.label.clutter_text.line_wrap = true; this.label.clutter_text.line_wrap_mode = Pango.WrapMode.WORD; this.label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; this.label.clutter_text.use_markup = (this.markup); this._bin.child.add_child(this.label); } Main.layoutManager.uiGroup.add_child(this._bin); Main.layoutManager.uiGroup.set_child_above_sibling(this._bin, null); } else if (this.custom) { this._bin.child = this.custom; } else { if (this._bin.child.icon) this._bin.child.icon.destroy(); if (this.gicon) { this._bin.child.icon = new St.Icon({gicon: this.gicon}); this._bin.child.insert_child_at_index(this._bin.child.icon, 0); } this.label.clutter_text.text = this.markup || this.text; this.label.clutter_text.use_markup = (this.markup); } // Position tooltip let [x, y] = this.parent.get_transformed_position(); x = (x + (this.parent.width / 2)) - Math.round(this._bin.width / 2); x += this.x_offset; y += this.y_offset; // Show tooltip if (this._showing) { this._bin.ease({ x: x, y: y, time: 0.15, transition: Clutter.AnimationMode.EASE_OUT_QUAD, }); } else { this._bin.set_position(x, y); this._bin.ease({ opacity: 232, time: 0.15, transition: Clutter.AnimationMode.EASE_OUT_QUAD, }); this._showing = true; } // Enable browse mode TOOLTIP_BROWSE_MODE = true; if (TOOLTIP_BROWSE_ID) { GLib.source_remove(TOOLTIP_BROWSE_ID); TOOLTIP_BROWSE_ID = 0; } if (this._hoverTimeoutId) { GLib.source_remove(this._hoverTimeoutId); this._hoverTimeoutId = 0; } } _hide() { if (this._bin) { this._bin.ease({ opacity: 0, time: 0.10, transition: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => { Main.layoutManager.uiGroup.remove_actor(this._bin); if (this.custom) this._bin.remove_child(this.custom); this._bin.destroy(); this._bin = null; }, }); } TOOLTIP_BROWSE_ID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => { TOOLTIP_BROWSE_MODE = false; TOOLTIP_BROWSE_ID = 0; return false; }); if (this._hoverTimeoutId) { GLib.source_remove(this._hoverTimeoutId); this._hoverTimeoutId = 0; } this._showing = false; this._hoverTimeoutId = 0; } _onHover() { if (this.parent.hover) { if (!this._hoverTimeoutId) { if (this._showing) { this._show(); } else { this._hoverTimeoutId = GLib.timeout_add( GLib.PRIORITY_DEFAULT, (TOOLTIP_BROWSE_MODE) ? 60 : 500, () => { this._show(); this._hoverTimeoutId = 0; return false; } ); } } } else { this._hide(); } } destroy() { this.parent.disconnect(this._destroyId); this.parent.disconnect(this._hoverId); this.parent.disconnect(this._buttonPressEventId); if (this.custom) this.custom.destroy(); if (this._bin) { Main.layoutManager.uiGroup.remove_actor(this._bin); this._bin.destroy(); } if (TOOLTIP_BROWSE_ID) { GLib.source_remove(TOOLTIP_BROWSE_ID); TOOLTIP_BROWSE_ID = 0; } if (this._hoverTimeoutId) { GLib.source_remove(this._hoverTimeoutId); this._hoverTimeoutId = 0; } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/shell/utils.js��������������������������������0000664�0000000�0000000�00000022751�14607666711�0025447�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Gtk from 'gi://Gtk'; import Config from '../config.js'; let St = null; // St is not available for prefs.js importing this file. try { St = (await import('gi://St')).default; } catch (e) { } /** * Get a themed icon, using fallbacks from GSConnect's GResource when necessary. * * @param {string} name - A themed icon name * @return {Gio.Icon} A themed icon */ export function getIcon(name) { if (getIcon._resource === undefined) { // Setup the desktop icons const settings = St.Settings.get(); getIcon._desktop = new Gtk.IconTheme(); getIcon._desktop.set_theme_name(settings.gtk_icon_theme); settings.connect('notify::gtk-icon-theme', (settings_, key_) => { getIcon._desktop.set_theme_name(settings_.gtk_icon_theme); }); // Preload our fallbacks const iconPath = 'resource://org/gnome/Shell/Extensions/GSConnect/icons'; const iconNames = [ 'org.gnome.Shell.Extensions.GSConnect', 'org.gnome.Shell.Extensions.GSConnect-symbolic', 'computer-symbolic', 'laptop-symbolic', 'smartphone-symbolic', 'tablet-symbolic', 'tv-symbolic', 'phonelink-ring-symbolic', 'sms-symbolic', ]; getIcon._resource = {}; for (const iconName of iconNames) { getIcon._resource[iconName] = new Gio.FileIcon({ file: Gio.File.new_for_uri(`${iconPath}/${iconName}.svg`), }); } } // Check the desktop icon theme if (getIcon._desktop.has_icon(name)) return new Gio.ThemedIcon({name: name}); // Check our GResource if (getIcon._resource[name] !== undefined) return getIcon._resource[name]; // Fallback to hoping it's in the theme somewhere return new Gio.ThemedIcon({name: name}); } /** * Get the contents of a GResource file, replacing `@PACKAGE_DATADIR@` where * necessary. * * @param {string} relativePath - A path relative to GSConnect's resource path * @return {string} The file contents as a string */ function getResource(relativePath) { try { const bytes = Gio.resources_lookup_data( GLib.build_filenamev([Config.APP_PATH, relativePath]), Gio.ResourceLookupFlags.NONE ); const source = new TextDecoder().decode(bytes.toArray()); return source.replace('@PACKAGE_DATADIR@', Config.PACKAGE_DATADIR); } catch (e) { logError(e, 'GSConnect'); return null; } } /** * Install file contents, to an absolute directory path. * * @param {string} dirname - An absolute directory path * @param {string} basename - The file name * @param {string} contents - The file contents * @return {boolean} A success boolean */ function _installFile(dirname, basename, contents) { try { const filename = GLib.build_filenamev([dirname, basename]); GLib.mkdir_with_parents(dirname, 0o755); return GLib.file_set_contents(filename, contents); } catch (e) { logError(e, 'GSConnect'); return false; } } /** * Install file contents from a GResource, to an absolute directory path. * * @param {string} dirname - An absolute directory path * @param {string} basename - The file name * @param {string} relativePath - A path relative to GSConnect's resource path * @return {boolean} A success boolean */ function _installResource(dirname, basename, relativePath) { try { const contents = getResource(relativePath); return _installFile(dirname, basename, contents); } catch (e) { logError(e, 'GSConnect'); return false; } } /** * Use Gio.File to ensure a file's executable bits are set. * * @param {string} filepath - An absolute path to a file * @returns {boolean} - True if the file already was, or is now, executable */ function _setExecutable(filepath) { try { const file = Gio.File.new_for_path(filepath); const finfo = file.query_info( `${Gio.FILE_ATTRIBUTE_STANDARD_TYPE},${Gio.FILE_ATTRIBUTE_UNIX_MODE}`, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); if (!finfo.has_attribute(Gio.FILE_ATTRIBUTE_UNIX_MODE)) return false; const mode = finfo.get_attribute_uint32( Gio.FILE_ATTRIBUTE_UNIX_MODE); const new_mode = (mode | 0o111); if (mode === new_mode) return true; return file.set_attribute_uint32( Gio.FILE_ATTRIBUTE_UNIX_MODE, new_mode, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); } catch (e) { logError(e, 'GSConnect'); return false; } } /** * Ensure critical files in the extension directory have the * correct permissions. */ export function ensurePermissions() { if (Config.IS_USER) { const executableFiles = [ 'gsconnect-preferences', 'service/daemon.js', 'service/nativeMessagingHost.js', ]; for (const file of executableFiles) _setExecutable(GLib.build_filenamev([Config.PACKAGE_DATADIR, file])); } } /** * Install the files necessary for the GSConnect service to run. */ export function installService() { const settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect', null ), path: '/org/gnome/shell/extensions/gsconnect/', }); const confDir = GLib.get_user_config_dir(); const dataDir = GLib.get_user_data_dir(); const homeDir = GLib.get_home_dir(); // DBus Service const dbusDir = GLib.build_filenamev([dataDir, 'dbus-1', 'services']); const dbusFile = `${Config.APP_ID}.service`; // Desktop Entry const appDir = GLib.build_filenamev([dataDir, 'applications']); const appFile = `${Config.APP_ID}.desktop`; const appPrefsFile = `${Config.APP_ID}.Preferences.desktop`; // Application Icon const iconDir = GLib.build_filenamev([dataDir, 'icons', 'hicolor', 'scalable', 'apps']); const iconFull = `${Config.APP_ID}.svg`; const iconSym = `${Config.APP_ID}-symbolic.svg`; // File Manager Extensions const fileManagers = [ [`${dataDir}/nautilus-python/extensions`, 'nautilus-gsconnect.py'], [`${dataDir}/nemo-python/extensions`, 'nemo-gsconnect.py'], ]; // WebExtension Manifests const manifestFile = 'org.gnome.shell.extensions.gsconnect.json'; const google = getResource(`webextension/${manifestFile}.google.in`); const mozilla = getResource(`webextension/${manifestFile}.mozilla.in`); const manifests = [ [`${confDir}/chromium/NativeMessagingHosts/`, google], [`${confDir}/google-chrome/NativeMessagingHosts/`, google], [`${confDir}/google-chrome-beta/NativeMessagingHosts/`, google], [`${confDir}/google-chrome-unstable/NativeMessagingHosts/`, google], [`${confDir}/BraveSoftware/Brave-Browser/NativeMessagingHosts/`, google], [`${confDir}/BraveSoftware/Brave-Browser-Beta/NativeMessagingHosts/`, google], [`${confDir}/BraveSoftware/Brave-Browser-Nightly/NativeMessagingHosts/`, google], [`${homeDir}/.mozilla/native-messaging-hosts/`, mozilla], [`${homeDir}/.config/microsoft-edge-dev/NativeMessagingHosts`, google], [`${homeDir}/.config/microsoft-edge-beta/NativeMessagingHosts`, google], ]; // If running as a user extension, ensure the DBus service, desktop entry, // file manager scripts, and WebExtension manifests are installed. if (Config.IS_USER) { // DBus Service if (!_installResource(dbusDir, dbusFile, `${dbusFile}.in`)) throw Error('GSConnect: Failed to install DBus Service'); // Desktop Entries _installResource(appDir, appFile, appFile); _installResource(appDir, appPrefsFile, appPrefsFile); // Application Icon _installResource(iconDir, iconFull, `icons/${iconFull}`); _installResource(iconDir, iconSym, `icons/${iconSym}`); // File Manager Extensions const target = `${Config.PACKAGE_DATADIR}/nautilus-gsconnect.py`; for (const [dir, name] of fileManagers) { const script = Gio.File.new_for_path(GLib.build_filenamev([dir, name])); if (!script.query_exists(null)) { GLib.mkdir_with_parents(dir, 0o755); script.make_symbolic_link(target, null); } } // WebExtension Manifests if (settings.get_boolean('create-native-messaging-hosts')) { for (const [dirname, contents] of manifests) _installFile(dirname, manifestFile, contents); } // Otherwise, if running as a system extension, ensure anything previously // installed when running as a user extension is removed. } else { GLib.unlink(GLib.build_filenamev([dbusDir, dbusFile])); GLib.unlink(GLib.build_filenamev([appDir, appFile])); GLib.unlink(GLib.build_filenamev([appDir, appPrefsFile])); GLib.unlink(GLib.build_filenamev([iconDir, iconFull])); GLib.unlink(GLib.build_filenamev([iconDir, iconSym])); for (const [dir, name] of fileManagers) GLib.unlink(GLib.build_filenamev([dir, name])); for (const manifest of manifests) GLib.unlink(GLib.build_filenamev([manifest[0], manifestFile])); } } �����������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/stylesheet.css��������������������������������0000664�0000000�0000000�00000004651�14607666711�0025544�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect * * SPDX-License-Identifier: GPL-2.0-or-later */ /* Device Menu PopupMenu.PopupMenuSection.gsconnect-device-section PopupMenu.PopupMenuSection.gsconnect-device-menu PopupMenu.PopupSeparatorMenuItem StLabel.gsconnect-device-name StBoxLayout.gsconnect-device-battery PopupMenu.PopupMenuSection StBoxLayout.gsconnect-list-box StBoxLayout (Submenu Container) */ .gsconnect-device-section { } /* Title Bar */ .gsconnect-device-name { font-weight: bold; } .gsconnect-device-menu .popup-separator-menu-item { margin-left: 0; margin-right: 0; } /* Battery Widget */ .gsconnect-device-battery { spacing: 3px; } .gsconnect-device-battery StLabel { font-size: 0.75em; } .gsconnect-device-battery StIcon { icon-size: 16px; } /* Signal Strength Widget */ .gsconnect-device-signal-strength { spacing: 3px; } .gsconnect-device-signal-strength StLabel { font-size: 0.75em; } .gsconnect-device-signal-strength StIcon { icon-size: 16px; } /* List Box */ .gsconnect-list-box { } /* Device Panel Indicator PanelMenu.Button.gsconnect-device-indicator PopupMenu.PopupMenu PopupMenu.PopupMenuSection.gsconnect-device-menu PopupMenu.PopupSeparatorMenuItem StLabel.gsconnect-device-name StBoxLayout.gsconnect-device-battery PopupMenu.PopupMenuSection StBoxLayout.gsconnect-icon-box StBoxLayout (Submenu Container) */ .gsconnect-device-indicator { -st-icon-style: symbolic; } /* Icon Box */ .gsconnect-icon-box { margin: 0em 2em 0.5em; spacing: 6px; } .gsconnect-icon-button { border-radius: 1em; padding: 0.5em; } .gsconnect-icon-button:hover, .gsconnect-icon-button:focus { background-color: rgba(255, 255, 255, 0.125); } .gsconnect-icon-button StIcon { icon-size: 1em; } /* Tooltip StBin.gsconnect-tooltip (inherits from .osd-window) StBoxLayout || [ Custom ClutterActor ] StIcon StLabel */ .gsconnect-tooltip { border-radius: 3px; min-width: 0; min-height: 0; padding: 6px; } .gsconnect-tooltip > StBoxLayout { spacing: 6px; } .gsconnect-tooltip StIcon { icon-size: 16px; } .gsconnect-tooltip StLabel { font-weight: normal; text-align: left; } .gsconnect-tooltip StLabel:rtl { text-align: right; } ���������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/utils/����������������������������������������0000775�0000000�0000000�00000000000�14607666711�0023773�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/utils/remote.js�������������������������������0000664�0000000�0000000�00000034125�14607666711�0025631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; const SERVICE_NAME = 'org.gnome.Shell.Extensions.GSConnect'; const SERVICE_PATH = '/org/gnome/Shell/Extensions/GSConnect'; const DEVICE_NAME = 'org.gnome.Shell.Extensions.GSConnect.Device'; const _PROPERTIES = { 'Connected': 'connected', 'EncryptionInfo': 'encryption-info', 'IconName': 'icon-name', 'Id': 'id', 'Name': 'name', 'Paired': 'paired', 'Type': 'type', }; function _proxyInit(proxy, cancellable = null) { if (proxy.__initialized !== undefined) return Promise.resolve(); return new Promise((resolve, reject) => { proxy.init_async( GLib.PRIORITY_DEFAULT, cancellable, (proxy, res) => { try { proxy.init_finish(res); proxy.__initialized = true; resolve(); } catch (e) { Gio.DBusError.strip_remote_error(e); reject(e); } } ); }); } /** * A simple proxy wrapper for devices exported over DBus. */ export const Device = GObject.registerClass({ GTypeName: 'GSConnectRemoteDevice', Implements: [Gio.DBusInterface], Properties: { 'connected': GObject.ParamSpec.boolean( 'connected', 'Connected', 'Whether the device is connected', GObject.ParamFlags.READABLE, null ), 'encryption-info': GObject.ParamSpec.string( 'encryption-info', 'Encryption Info', 'A formatted string with the local and remote fingerprints', GObject.ParamFlags.READABLE, null ), 'icon-name': GObject.ParamSpec.string( 'icon-name', 'Icon Name', 'Icon name representing the device', GObject.ParamFlags.READABLE, null ), 'id': GObject.ParamSpec.string( 'id', 'deviceId', 'The device hostname or other unique id', GObject.ParamFlags.READABLE, '' ), 'name': GObject.ParamSpec.string( 'name', 'deviceName', 'The device name', GObject.ParamFlags.READABLE, null ), 'paired': GObject.ParamSpec.boolean( 'paired', 'Paired', 'Whether the device is paired', GObject.ParamFlags.READABLE, null ), 'type': GObject.ParamSpec.string( 'type', 'deviceType', 'The device type', GObject.ParamFlags.READABLE, null ), }, }, class Device extends Gio.DBusProxy { _init(service, object_path) { this._service = service; super._init({ g_connection: service.g_connection, g_name: SERVICE_NAME, g_object_path: object_path, g_interface_name: DEVICE_NAME, }); } vfunc_g_properties_changed(changed, invalidated) { try { for (const name in changed.deepUnpack()) this.notify(_PROPERTIES[name]); } catch (e) { logError(e); } } _get(name, fallback = null) { try { return this.get_cached_property(name).unpack(); } catch (e) { return fallback; } } get connected() { return this._get('Connected', false); } get encryption_info() { return this._get('EncryptionInfo', ''); } get icon_name() { return this._get('IconName', 'computer'); } get id() { return this._get('Id', '0'); } get name() { return this._get('Name', 'Unknown'); } get paired() { return this._get('Paired', false); } get service() { return this._service; } get type() { return this._get('Type', 'desktop'); } async start() { try { await _proxyInit(this); // For GActions & GMenu we pass the service's name owner to avoid // any mixup with instances. this.action_group = Gio.DBusActionGroup.get( this.g_connection, this.service.g_name_owner, this.g_object_path ); this.menu = Gio.DBusMenuModel.get( this.g_connection, this.service.g_name_owner, this.g_object_path ); // Poke the GMenu to ensure it's ready for us await new Promise((resolve, reject) => { this.g_connection.call( SERVICE_NAME, this.g_object_path, 'org.gtk.Menus', 'Start', new GLib.Variant('(au)', [[0]]), null, Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => { try { resolve(proxy.call_finish(res)); } catch (e) { Gio.DBusError.strip_remote_error(e); reject(e); } } ); }); } catch (e) { this.destroy(); throw e; } } destroy() { GObject.signal_handlers_destroy(this); } }); /** * A simple proxy wrapper for the GSConnect service. */ export const Service = GObject.registerClass({ GTypeName: 'GSConnectRemoteService', Implements: [Gio.DBusInterface], Properties: { 'active': GObject.ParamSpec.boolean( 'active', 'Active', 'Whether the service is active', GObject.ParamFlags.READABLE, false ), }, Signals: { 'device-added': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [Device.$gtype], }, 'device-removed': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [Device.$gtype], }, }, }, class Service extends Gio.DBusProxy { _init() { super._init({ g_bus_type: Gio.BusType.SESSION, g_name: SERVICE_NAME, g_object_path: SERVICE_PATH, g_interface_name: 'org.freedesktop.DBus.ObjectManager', g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION, }); this._active = false; this._devices = new Map(); this._starting = false; // Watch the service this._nameOwnerChangedId = this.connect( 'notify::g-name-owner', this._onNameOwnerChanged.bind(this) ); } get active() { return this._active; } get devices() { return Array.from(this._devices.values()); } vfunc_g_signal(sender_name, signal_name, parameters) { try { // Don't emit signals until the ObjectManager has started if (!this.active) return; parameters = parameters.deepUnpack(); switch (true) { case (signal_name === 'InterfacesAdded'): this._onInterfacesAdded(...parameters); break; case (signal_name === 'InterfacesRemoved'): this._onInterfacesRemoved(...parameters); break; } } catch (e) { logError(e); } } /** * org.freedesktop.DBus.ObjectManager.InterfacesAdded * * @param {string} object_path - Path interfaces have been added to * @param {Object} interfaces - A dictionary of interface objects */ async _onInterfacesAdded(object_path, interfaces) { try { // An empty list means only the object has been added if (Object.values(interfaces).length === 0) return; // Skip existing proxies if (this._devices.has(object_path)) return; // Create a proxy const device = new Device(this, object_path); await device.start(); // Hold the proxy and emit ::device-added this._devices.set(object_path, device); this.emit('device-added', device); } catch (e) { logError(e, object_path); } } /** * org.freedesktop.DBus.ObjectManager.InterfacesRemoved * * @param {string} object_path - Path interfaces have been removed from * @param {string[]} interfaces - List of interface names removed */ _onInterfacesRemoved(object_path, interfaces) { try { // An empty interface list means the object is being removed if (interfaces.length === 0) return; // Get the proxy const device = this._devices.get(object_path); if (device === undefined) return; // Release the proxy and emit ::device-removed this._devices.delete(object_path); this.emit('device-removed', device); // Destroy the device and force disposal device.destroy(); } catch (e) { logError(e, object_path); } } async _addDevices() { const objects = await new Promise((resolve, reject) => { this.call( 'GetManagedObjects', null, Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => { try { const variant = proxy.call_finish(res); resolve(variant.deepUnpack()[0]); } catch (e) { Gio.DBusError.strip_remote_error(e); reject(e); } } ); }); for (const [object_path, object] of Object.entries(objects)) await this._onInterfacesAdded(object_path, object); } _clearDevices() { for (const [object_path, device] of this._devices) { this._devices.delete(object_path); this.emit('device-removed', device); device.destroy(); } } async _onNameOwnerChanged() { try { // If the service stopped, remove each device and mark it inactive if (this.g_name_owner === null) { this._clearDevices(); this._active = false; this.notify('active'); // If the service started, mark it active and add each device } else { this._active = true; this.notify('active'); await this._addDevices(); } } catch (e) { logError(e); } } /** * Reload all devices without affecting the remote service. This amounts to * removing and adding each device while emitting the appropriate signals. */ async reload() { try { if (this._starting === false) { this._starting = true; this._clearDevices(); await _proxyInit(this); await this._onNameOwnerChanged(); this._starting = false; } } catch (e) { this._starting = false; throw e; } } /** * Start the service */ async start() { try { if (this._starting === false && this.active === false) { this._starting = true; await _proxyInit(this); await this._onNameOwnerChanged(); // Activate the service if it's not already running if (!this.active) { await new Promise((resolve, reject) => { this.g_connection.call( SERVICE_NAME, SERVICE_PATH, 'org.freedesktop.Application', 'Activate', GLib.Variant.new('(a{sv})', [{}]), null, Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => { try { resolve(proxy.call_finish(res)); } catch (e) { Gio.DBusError.strip_remote_error(e); reject(e); } } ); }); } this._starting = false; } } catch (e) { this._starting = false; throw e; } } /** * Stop the service */ stop() { if (this.active) this.activate_action('quit'); } activate_action(name, parameter = null) { try { const paramArray = []; if (parameter instanceof GLib.Variant) paramArray[0] = parameter; const connection = this.g_connection || Gio.DBus.session; connection.call( SERVICE_NAME, SERVICE_PATH, 'org.freedesktop.Application', 'ActivateAction', GLib.Variant.new('(sava{sv})', [name, paramArray, {}]), null, Gio.DBusCallFlags.NONE, -1, null, null ); } catch (e) { logError(e); } } destroy() { if (this._nameOwnerChangedId > 0) { this.disconnect(this._nameOwnerChangedId); this._nameOwnerChangedId = 0; this._clearDevices(); this._active = false; GObject.signal_handlers_destroy(this); } } }); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/utils/setup.js��������������������������������0000664�0000000�0000000�00000003014�14607666711�0025467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Gettext from 'gettext'; import Config from '../config.js'; /** * Initialise and setup Gettext. */ export function setupGettext() { // Init Gettext String.prototype.format = imports.format.format; Gettext.bindtextdomain(Config.APP_ID, Config.PACKAGE_LOCALEDIR); globalThis._ = GLib.dgettext.bind(null, Config.APP_ID); globalThis.ngettext = GLib.dngettext.bind(null, Config.APP_ID); } /** * Initialise and setup Config, GResources and GSchema. * @param {string} extensionPath - The absolute path to the extension directory */ export default function setup(extensionPath) { // Ensure config.js is setup properly Config.PACKAGE_DATADIR = extensionPath; const userDir = GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell']); if (Config.PACKAGE_DATADIR.startsWith(userDir)) { Config.IS_USER = true; Config.GSETTINGS_SCHEMA_DIR = `${Config.PACKAGE_DATADIR}/schemas`; Config.PACKAGE_LOCALEDIR = `${Config.PACKAGE_DATADIR}/locale`; } // Init GResources Gio.Resource.load( GLib.build_filenamev([Config.PACKAGE_DATADIR, `${Config.APP_ID}.gresource`]) )._register(); // Init GSchema Config.GSCHEMA = Gio.SettingsSchemaSource.new_from_directory( Config.GSETTINGS_SCHEMA_DIR, Gio.SettingsSchemaSource.get_default(), false ); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/src/wl_clipboard.js�������������������������������0000664�0000000�0000000�00000020623�14607666711�0025635�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: JingMatrix https://github.com/JingMatrix // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GjsPrivate from 'gi://GjsPrivate'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; // laucher for wl-clipboard const launcher = new Gio.SubprocessLauncher({ flags: Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_MERGE, }); /* * DBus Interface Info */ const DBUS_NAME = 'org.gnome.Shell.Extensions.GSConnect.Clipboard'; const DBUS_PATH = '/org/gnome/Shell/Extensions/GSConnect/Clipboard'; const DBUS_NODE = Gio.DBusNodeInfo.new_for_xml(` <node> <interface name="org.gnome.Shell.Extensions.GSConnect.Clipboard"> <!-- Methods --> <method name="GetMimetypes"> <arg direction="out" type="as" name="mimetypes"/> </method> <method name="GetText"> <arg direction="out" type="s" name="text"/> </method> <method name="SetText"> <arg direction="in" type="s" name="text"/> </method> <!-- Signals --> <signal name="OwnerChange"/> </interface> </node> `); const DBUS_INFO = DBUS_NODE.lookup_interface(DBUS_NAME); /* * Text Mimetypes */ const TEXT_MIMETYPES = [ 'text/plain;charset=utf-8', 'UTF8_STRING', 'text/plain', 'STRING', ]; /* GSConnectClipboardPortal: * * A simple clipboard portal, especially useful on Wayland where GtkClipboard * doesn't work in the background. */ export const Clipboard = GObject.registerClass( { GTypeName: 'GSConnectShellClipboard', }, class GSConnectShellClipboard extends GjsPrivate.DBusImplementation { _init() { super._init({ g_interface_info: DBUS_INFO, }); this._transferring = false; this.watcher = Gio.Subprocess.new([ 'wl-paste', '-w', 'dbus-send', DBUS_PATH, '--dest=' + DBUS_NAME, DBUS_NAME + '.OwnerChange', ], Gio.SubprocessFlags.NONE); // Prepare DBus interface this._handleMethodCallId = this.connect( 'handle-method-call', this._onHandleMethodCall.bind(this) ); this._nameId = Gio.DBus.own_name( Gio.BusType.SESSION, DBUS_NAME, Gio.BusNameOwnerFlags.NONE, this._onBusAcquired.bind(this), null, this._onNameLost.bind(this) ); } _onBusAcquired(connection, name) { try { this.export(connection, DBUS_PATH); } catch (e) { logError(e); } } _onNameLost(connection, name) { try { this.unexport(); } catch (e) { logError(e); } } async _onHandleMethodCall(iface, name, parameters, invocation) { let retval; try { const args = parameters.recursiveUnpack(); retval = await this[name](...args); } catch (e) { if (e instanceof GLib.Error) { invocation.return_gerror(e); } else { if (!e.name.includes('.')) e.name = `org.gnome.gjs.JSError.${e.name}`; invocation.return_dbus_error(e.name, e.message); } return; } if (retval === undefined) retval = new GLib.Variant('()', []); try { if (!(retval instanceof GLib.Variant)) { const args = DBUS_INFO.lookup_method(name).out_args; retval = new GLib.Variant( `(${args.map((arg) => arg.signature).join('')})`, args.length === 1 ? [retval] : retval ); } invocation.return_value(retval); // Without a response, the client will wait for timeout } catch (e) { invocation.return_dbus_error( 'org.gnome.gjs.JSError.ValueError', 'Service implementation returned an incorrect value type' ); } } /** * Get the available mimetypes of the current clipboard content * * @return {Promise<string[]>} A list of mime-types */ GetMimetypes() { return new Promise((resolve, reject) => { const proc = launcher.spawnv([ 'wl-paste', '--list-types', '-n', ]); proc.communicate_utf8_async(null, null, (proc, res) => { try { const [, stdout, stderr] = proc.communicate_utf8_finish(res); if (proc.get_successful()) resolve(stdout.trim().split('\n')); else logError(stderr); } catch (e) { reject(e); } }); }); } /** * Get the text content of the clipboard * * @return {Promise<string>} Text content of the clipboard */ GetText() { return new Promise((resolve, reject) => { this.GetMimetypes().then((mimetypes) => { const mimetype = TEXT_MIMETYPES.find((type) => mimetypes.includes(type) ); if (mimetype !== undefined) { const proc = launcher.spawnv(['wl-paste', '-n']); proc.communicate_utf8_async(null, null, (proc, res) => { try { const [, stdout, stderr] = proc.communicate_utf8_finish(res); if (proc.get_successful()) resolve(stdout); else logError(stderr); } catch (e) { reject(e); } }); } else { reject(new Error('text not available')); } }); }); } /** * Set the text content of the clipboard * * @param {string} text - text content to set * @return {Promise} A promise for the operation */ SetText(text) { return new Promise((resolve, reject) => { try { if (typeof text !== 'string') { throw new Gio.DBusError({ code: Gio.DBusError.INVALID_ARGS, message: 'expected string', }); } launcher.spawnv(['wl-copy', text]); resolve(); } catch (e) { reject(e); } }); } destroy() { if (this._nameId > 0) { Gio.bus_unown_name(this._nameId); this._nameId = 0; } if (this._handleMethodCallId > 0) { this.disconnect(this._handleMethodCallId); this._handleMethodCallId = 0; this.unexport(); } if (this.watcher) this.watcher.force_exit(); } } ); let _portal = null; let _portalId = 0; /** * Watch for the service to start and export the clipboard portal when it does. */ export function watchService() { if (_portalId > 0) return; _portalId = Gio.bus_watch_name( Gio.BusType.SESSION, 'org.gnome.Shell.Extensions.GSConnect', Gio.BusNameWatcherFlags.NONE, () => { if (_portal === null) _portal = new Clipboard(); }, () => { if (_portal !== null) { _portal.destroy(); _portal = null; } } ); } /** * Stop watching the service and export the portal if currently running. */ export function unwatchService() { if (_portalId > 0) { Gio.bus_unwatch_name(_portalId); _portalId = 0; } } // vim:tabstop=2:shiftwidth=2:expandtab �������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/�������������������������������������0000775�0000000�0000000�00000000000�14607666711�0024556�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/.editorconfig������������������������0000664�0000000�0000000�00000000457�14607666711�0027241�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # Manifests: 2-space indents [manifest.*.json] indent_style = space indent_size = 2 # l10n message files: 4-space indents [_locales/**.json] indent_style = space indent_size = 4 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/.eslintrc.json�����������������������0000664�0000000�0000000�00000000513�14607666711�0027351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "env": { "browser": true, "webextensions": true }, "ignorePatterns": ["browser-polyfill.*"], "rules": { "no-console": [ "error", { "allow": [ "warn", "error" ] } ] } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/.eslintrc.json.license���������������0000664�0000000�0000000�00000000164�14607666711�0030774�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/����������������������������0000775�0000000�0000000�00000000000�14607666711�0026337�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ar/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026741�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ar/messages.json������������0000664�0000000�0000000�00000003251�14607666711�0031444�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "مشاركة الروابط مع GSConnectØŒ مباشرة إلى Ø§Ù„Ù…ØªØµÙØ­ أو بواسطة الرسائل القصيرة.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "إرسال إلى جهاز الجوال", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "الخدمة غير متاحة", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "لم يتم العثور على أي جهاز", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "ÙØªØ­ ÙÙŠ Ø§Ù„Ù…ØªØµÙØ­", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "إرسال الرسائل القصيرة", "description": "Context Menu label for sharing a link in an SMS message" } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ar/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/be/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026725�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/be/messages.json������������0000664�0000000�0000000�00000003232�14607666711�0031427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Ðбагульвайце ÑпаÑылкі з GSConnect, напрамую Ñž браўзер або праз SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Ðдправіць на мабільную прыладу", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "СÑÑ€Ð²Ñ–Ñ Ð½ÐµÐ´Ð°Ñтупны", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Ðе знойдзена прылад", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Ðдкрыць у браўзеры", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Ðдправіць SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/be/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ca/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026722�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ca/messages.json������������0000664�0000000�0000000�00000003071�14607666711�0031425�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Compartiu enllaços amb el GSConnect, directament al navegador o mitjançant SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Envia al dispositiu mòbil", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "El servei no està disponible", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "No s’ha trobat cap dispositiu", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Obre al navegador", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Envia un SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ca/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033046�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/cs/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026744�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/cs/messages.json������������0000664�0000000�0000000�00000003101�14607666711�0031441�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Sdílet odkazy pomocí GSConnect, přímo do prohlížeÄe nebo prostÅ™ednictvím SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Odeslat do mobilního zařízení", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Služba nedostupná", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Nenalezeno žádné zařízení", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Otevřít v prohlížeÄi", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Poslat SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/cs/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033070�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/da/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026723�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/da/messages.json������������0000664�0000000�0000000�00000003015�14607666711�0031424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Dele links med GSConnect, direkte til browseren eller via SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Send til Mobil Enhed", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Tjenesten er ikke tilgængelig", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Ingen Enhed Fundet", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Ã…bn i browser", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Send SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/da/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033047�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/de/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026727�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/de/messages.json������������0000664�0000000�0000000�00000003025�14607666711�0031431�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Verweise mit GSConnect direkt an den Browser oder per SMS freigeben.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "An Mobilgerät senden", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Dienst nicht verfügbar", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Kein Gerät gefunden", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Im Browser öffnen", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "SMS senden", "description": "Context Menu label for sharing a link in an SMS message" } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/de/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033053�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/el/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026737�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/el/messages.json������������0000664�0000000�0000000�00000003215�14607666711�0031442�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Share links with GSConnect, direct to the browser or by SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Αποστολή σε φοÏητή συσκευή", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Η υπηÏεσία δεν είναι διαθέσιμη", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Δεν βÏέθηκε συσκευή", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Άνοιγμα στο Ï€ÏόγÏαμμα πεÏιήγησης", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Στείλτε SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/el/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033063�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/en/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026741�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/en/messages.json������������0000664�0000000�0000000�00000002777�14607666711�0031460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Share links with GSConnect, direct to the browser or by SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Send To Mobile Device", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Service Unavailable", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "No Device Found", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Open in Browser", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Send SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/en/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/es/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026746�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/es/messages.json������������0000664�0000000�0000000�00000003065�14607666711�0031454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Comparta enlaces con GSConnect, directamente al navegador o a través de SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Enviar a dispositivo móvil", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Servicio no disponible", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "No se encontró ningún dispositivo", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Abrir en el navegador", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Enviar SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/es/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/et/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026747�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/et/messages.json������������0000664�0000000�0000000�00000002775�14607666711�0031464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Jaga linke GSConnectiga, otse brauserisse või SMSi teel.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Saada mobiilseadmesse", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Teenus pole saadaval", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Seadet ei leitud", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Ava brauseris", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Saada SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ���GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/et/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fa-IR/����������������������0000775�0000000�0000000�00000000000�14607666711�0027235�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fa-IR/messages.json���������0000664�0000000�0000000�00000003254�14607666711�0031743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "جی‌اس‌کانکت", "description": "The extension name" }, "extensionDescription": { "message": "با جی‌اس کانکت، پیوندها را با پیامک یا مستقیماً در مرورگر هم‌رسانی کنید.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "ÙØ±Ø³ØªØ§Ø¯Ù† به Ø§ÙØ²Ø§Ø±Ù‡Ù” همراه", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "خدمت ناموجود", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "هیچ Ø§ÙØ²Ø§Ø±Ù‡â€ŒØ§ÛŒ پیدا نشد", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "گشودن در مرورگر", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "ÙØ±Ø³ØªØ§Ø¯Ù† پیامک", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fa-IR/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033361�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fa/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026725�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fa/messages.json������������0000664�0000000�0000000�00000003254�14607666711�0031433�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "جی‌اس‌کانکت", "description": "The extension name" }, "extensionDescription": { "message": "با جی‌اس کانکت، پیوندها را با پیامک یا مستقیماً در مرورگر هم‌رسانی کنید.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "ÙØ±Ø³ØªØ§Ø¯Ù† به Ø§ÙØ²Ø§Ø±Ù‡Ù” همراه", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "خدمت ناموجود", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "هیچ Ø§ÙØ²Ø§Ø±Ù‡â€ŒØ§ÛŒ پیدا نشد", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "گشودن در مرورگر", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "ÙØ±Ø³ØªØ§Ø¯Ù† پیامک", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fa/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fi/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026735�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fi/messages.json������������0000664�0000000�0000000�00000003064�14607666711�0031442�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Jaa linkkejä GSConnectin avulla suoraan selaimeen tai tekstiviestillä.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Lähetä mobiililaitteeseen", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Palvelu ei ole käytettävissä", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Laitetta Ei Löytynyt", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Avaa Selaimessa", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Lähetä tekstiviestillä", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fi/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fr/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026746�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fr/messages.json������������0000664�0000000�0000000�00000003061�14607666711�0031450�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Partagez des liens avec GSConnect, directement vers le navigateur ou par SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Envoyer vers l'appareil mobile", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Service indisponible", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Aucun appareil trouvé", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Ouvrir dans le navigateur", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Envoyer un SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fr/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fy-NL/����������������������0000775�0000000�0000000�00000000000�14607666711�0027264�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fy-NL/messages.json���������0000664�0000000�0000000�00000003040�14607666711�0031763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Keppelingen mei GSConnect ferstjoere, direkt nei de browser of mei SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Nei Mobiel Apparaat ferstjoere", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Tsjinst Ûnbeskikber", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Gjin Apparaat Fûn", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Yn Browser Iepenje", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "SMS Ferstjoere", "description": "Context Menu label for sharing a link in an SMS message" } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/fy-NL/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/gl/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026741�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/gl/messages.json������������0000664�0000000�0000000�00000003047�14607666711�0031447�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Compartir ligazóns con GSConnect, directamente co navegador ou por SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Enviar a dispositivo móbil", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Servizo non dispoñíbel", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Non se atopou o dispositivo", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Abrir no navegador", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Enviar SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/gl/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/hu/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026753�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/hu/messages.json������������0000664�0000000�0000000�00000003076�14607666711�0031463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Hivatkozások megosztása GSConnecttel, közvetlenül a böngészÅ‘bÅ‘l vagy SMS-ben.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Küldés mobileszközre", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "A szolgáltatás nem érhetÅ‘ el", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Nem található eszköz", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Megnyitás böngészÅ‘ben", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "SMS küldése", "description": "Context Menu label for sharing a link in an SMS message" } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/hu/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033077�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/id-ID/����������������������0000775�0000000�0000000�00000000000�14607666711�0027225�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/id-ID/messages.json���������0000664�0000000�0000000�00000003026�14607666711�0031730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Share links with GSConnect, direct to the browser or by SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Send To Mobile Device", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Layanan Tidak Tersedia", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Tidak Ada Perangkat Yang Ditemukan", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Buka di Browser", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Kirim SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/id-ID/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/it/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026753�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/it/messages.json������������0000664�0000000�0000000�00000003041�14607666711�0031453�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Condividi collegamenti con GSConnect, direttamente nel browser o via SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Invia al dispositivo mobile", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Servizio non disponibile", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Dispositivo non trovato", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Apri nel browser", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Invia SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/it/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033077�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ko-KR/����������������������0000775�0000000�0000000�00000000000�14607666711�0027262�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ko-KR/messages.json���������0000664�0000000�0000000�00000003124�14607666711�0031764�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "GSConnect로 ë§í¬ë¥¼ 공유하여 웹 브ë¼ìš°ì €ì—서 열거나 메시지를 통해 공유합니다.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "ëª¨ë°”ì¼ ê¸°ê¸°ë¡œ 전송", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "서비스 안 ë¨", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "장치를 ì°¾ì„ ìˆ˜ ì—†ìŒ", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "웹 브ë¼ìš°ì €ì—서 열기", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "메시지 보내기", "description": "Context Menu label for sharing a link in an SMS message" } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ko-KR/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033406�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ko/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026750�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ko/messages.json������������0000664�0000000�0000000�00000003057�14607666711�0031457�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "브ë¼ìš°ì €ë¡œ ì§ì ‘ ë˜ëŠ” SMS로 GSConnect와 ë§í¬ë¥¼ 공유합니다.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "ëª¨ë°”ì¼ ìž¥ì¹˜ë¡œ 보내기", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "서비스 불가", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "장치를 ì°¾ì„ ìˆ˜ ì—†ìŒ", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "브ë¼ìš°ì €ì—서 열기", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "SMS 보내기", "description": "Context Menu label for sharing a link in an SMS message" } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ko/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/lt/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026756�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/lt/messages.json������������0000664�0000000�0000000�00000003070�14607666711�0031460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Bendrinti nuorodas, naudojant GSConnect, tiesiogiai į narÅ¡yklÄ™ ar per SMS žinutÄ™.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Siųsti į mobilųjį įrenginį", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Paslauga neprieinama", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Nerasta jokių įrenginių", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Atverti narÅ¡yklÄ—je", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Siųsti SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/lt/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033102�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/nl-BE/����������������������0000775�0000000�0000000�00000000000�14607666711�0027234�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/nl-BE/messages.json���������0000664�0000000�0000000�00000003004�14607666711�0031733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Deel links met GSConnect, direct naar de browser of per SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Verzend naar GSM", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Dienst onbeschikbaar", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Geen toestel gevonden", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Open in browser", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Verzend SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/nl-BE/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/nl-NL/����������������������0000775�0000000�0000000�00000000000�14607666711�0027257�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/nl-NL/messages.json���������0000664�0000000�0000000�00000003032�14607666711�0031757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Deel links met GSConnect, direct naar de browser of via sms.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Versturen naar mobiel apparaat", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Dienst niet beschikbaar", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Geen apparaat gevonden", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Openen in browser", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "SMS versturen", "description": "Context Menu label for sharing a link in an SMS message" } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/nl-NL/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033403�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pl/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026752�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pl/messages.json������������0000664�0000000�0000000�00000003136�14607666711�0031457�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "UdostÄ™pnianie odnoÅ›ników za pomocÄ… GSConnect, bezpoÅ›rednio do przeglÄ…darki lub przez wiadomość SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "WyÅ›lij na urzÄ…dzenie mobilne", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "UsÅ‚uga jest niedostÄ™pna", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Nie odnaleziono żadnego urzÄ…dzenia", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Otwórz w przeglÄ…darce", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "WyÅ›lij SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pl/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033076�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pt-BR/����������������������0000775�0000000�0000000�00000000000�14607666711�0027263�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pt-BR/messages.json���������0000664�0000000�0000000�00000003051�14607666711�0031764�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Compartilhe links com o GSConnect, diretamente no navegador ou por SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Enviar para dispositivo móvel", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Serviço indisponível", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Nenhum dispositivo encontrado", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Abrir no navegador", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Enviar SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pt-BR/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pt-PT/����������������������0000775�0000000�0000000�00000000000�14607666711�0027303�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pt-PT/messages.json���������0000664�0000000�0000000�00000003060�14607666711�0032004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Partilhar ligações com o GSConnect, diretamente para o navegador ou por SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Enviar para dispositivo móvel", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Serviço indisponível", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Nenhum dispositivo encontrado", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Abrir no navegador", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Enviar SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/pt-PT/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ru/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026765�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ru/messages.json������������0000664�0000000�0000000�00000003160�14607666711�0031467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "ОтправлÑть ÑÑылки в веб браузер или по SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Отправить на уÑтройÑтво", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Ð¡ÐµÑ€Ð²Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупен", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "УÑтройÑтва не найдены", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Открыть в браузере", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Отправить СМС", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/ru/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033111�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sk/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026754�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sk/messages.json������������0000664�0000000�0000000�00000003076�14607666711�0031464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Zdieľanie odkazov s aplikáciou GSConnect, priamo cez prehliadaÄ alebo formou SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "OdoslaÅ¥ do mobilného zariadenia", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Služba nedostupná", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "NenaÅ¡lo sa žiadne zariadenie", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "OtvoriÅ¥ v prehliadaÄi", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "OdoslaÅ¥ SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sk/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sr-Latn/��������������������0000775�0000000�0000000�00000000000�14607666711�0027657�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sr-Latn/messages.json�������0000664�0000000�0000000�00000003025�14607666711�0032361�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Deli veze GSKonektom, direktno u pregldaÄ ili putem SMS-a.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "PoÅ¡alji na mobilni ureÄ‘aj", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Servis nije dostupan", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Nema naÄ‘enih ureÄ‘aja", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Otvori u pregledaÄu", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "PoÅ¡alji SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������messages.json.license�������������������������������������������������������������������������������0000664�0000000�0000000�00000000164�14607666711�0033724�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sr-Latn����������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sr/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026763�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sr/messages.json������������0000664�0000000�0000000�00000003216�14607666711�0031467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Дели везе ГСКонектом, директно у преглдач или путем СМС-а.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Пошаљи на мобилни уређај", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Ð¡ÐµÑ€Ð²Ð¸Ñ Ð½Ð¸Ñ˜Ðµ доÑтупан", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Ðема нађених уређаја", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Отвори у прегледачу", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Пошаљи СМС", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sr/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033107�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sv-SE/����������������������0000775�0000000�0000000�00000000000�14607666711�0027274�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sv-SE/messages.json���������0000664�0000000�0000000�00000003040�14607666711�0031773�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Dela länkar med GSConnect, direkt till webbläsaren eller med SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Skicka till mobil enhet", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Tjänsten är inte tillgänglig", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Ingen enhet hittades", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Öppna i webbläsare", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Skicka SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sv-SE/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sv/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026767�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sv/messages.json������������0000664�0000000�0000000�00000002777�14607666711�0031506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Share links with GSConnect, direct to the browser or by SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Send To Mobile Device", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Service Unavailable", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "No Device Found", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Open in Browser", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Send SMS", "description": "Context Menu label for sharing a link in an SMS message" } } �GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/sv/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033113�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/tr/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026764�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/tr/messages.json������������0000664�0000000�0000000�00000003021�14607666711�0031462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "GSConnect ile baÄŸlantıları doÄŸrudan tarayıcı veya SMS ile paylaÅŸ.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Mobil Cihaza Gönder", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Servis Mevcut DeÄŸil", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Cihaz Bulunamadı", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Tarayıcıda Aç", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "SMS Gönder", "description": "Context Menu label for sharing a link in an SMS message" } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/tr/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033110�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/uk/�������������������������0000775�0000000�0000000�00000000000�14607666711�0026756�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/uk/messages.json������������0000664�0000000�0000000�00000003277�14607666711�0031471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Поширюйте поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð·Ð° допомогою GSConnect, напрÑму до браузера або через SMS.", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Відправити до мобільного приÑтрою", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "Ð¡ÐµÑ€Ð²Ñ–Ñ Ð½ÐµÐ´Ð¾Ñтупний", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "ПриÑтроїв не знайдено", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Відкрити у браузері", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "Відправити SMS", "description": "Context Menu label for sharing a link in an SMS message" } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/uk/messages.json.license����0000664�0000000�0000000�00000000164�14607666711�0033102�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/zh-CN/����������������������0000775�0000000�0000000�00000000000�14607666711�0027256�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/zh-CN/messages.json���������0000664�0000000�0000000�00000003012�14607666711�0031754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "通过 GSConnect 直接将链接å‘é€åˆ°æµè§ˆå™¨æˆ–短信。", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "å‘é€åˆ°ç§»åŠ¨è®¾å¤‡", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "æœåŠ¡ä¸å¯ç”¨", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "没有找到设备", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "在æµè§ˆå™¨ä¸­æ‰“å¼€", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "å‘é€çŸ­ä¿¡", "description": "Context Menu label for sharing a link in an SMS message" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/zh-CN/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/zh-TW/����������������������0000775�0000000�0000000�00000000000�14607666711�0027310�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/zh-TW/messages.json���������0000664�0000000�0000000�00000002776�14607666711�0032026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "直接用ç€è¦½å™¨æˆ–經由簡訊和 GSConnect 共享éˆçµ", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "傳é€åˆ°æ‰‹æ©Ÿ", "description": "Top-level Context Menu label when multiple devices are available" }, "contextMenuSinglePlugin": { "message": "$NAME$ ($PLUGIN$)", "description": "Context Menu label for devices that only support a single plugin", "placeholders": { "name": { "content": "$1", "example": "Nexus 4" }, "plugin": { "content": "$2", "example": "Open in Browser" } } }, "popupMenuDisconnected": { "message": "æœå‹™ç„¡æ³•使用", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "找ä¸åˆ°è£ç½®", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "以ç€è¦½å™¨é–‹å•Ÿ", "description": "Context Menu label for opening a link in the device's web browser" }, "smsMessage": { "message": "發é€ç°¡è¨Š", "description": "Context Menu label for sharing a link in an SMS message" } } ��GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/_locales/zh-TW/messages.json.license�0000664�0000000�0000000�00000000164�14607666711�0033434�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/background.html����������������������0000664�0000000�0000000�00000000563�14607666711�0027567�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!-- SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later --> <!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> <body> <script src="js/browser-polyfill.min.js"></script> <script src="js/background.js"></script> </body> </html> ���������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/gettext.js���������������������������0000775�0000000�0000000�00000007152�14607666711�0026610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env -S gjs -m // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; // eslint-disable-next-line no-redeclare const _ = (msgid) => GLib.dgettext('org.gnome.Shell.Extensions.GSConnect', msgid); // POT Patterns const MSGID_REGEX = /^msgid "(.+)"$/; const MSGSTR_REGEX = /^msgstr "(.+)"$/; // MSGID -> MESSAGE Reverse Map const MSGID = { 'GSConnect': 'extensionName', 'Share links with GSConnect, direct to the browser or by SMS.': 'extensionDescription', 'Send To Mobile Device': 'contextMenuMultipleDevices', 'Service Unavailable': 'popupMenuDisconnected', 'No Device Found': 'popupMenuNoDevices', 'Open in Browser': 'shareMessage', 'Send SMS': 'smsMessage', }; // TRANSLATORS: Extension name _('GSConnect'); // TRANSLATORS: Chrome/Firefox WebExtension description _('Share links with GSConnect, direct to the browser or by SMS.'); // TRANSLATORS: Top-level context menu item for GSConnect _('Send To Mobile Device'); // TRANSLATORS: WebExtension can't connect to GSConnect _('Service Unavailable'); // TRANSLATORS: No devices are known or available _('No Device Found'); // TRANSLATORS: Open URL with the device's browser _('Open in Browser'); // TRANSLATORS: Share URL by SMS _('Send SMS'); // JSON.load = function (gfile) { try { const data = gfile.load_contents(null)[1]; if (data instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(data)); else return JSON.parse(data.toString()); } catch (e) { logError(e); return {}; } }; // Find the cwd, locale dir and po dir const cwd = Gio.File.new_for_path('.'); const localedir = cwd.get_child('_locales'); const podir = cwd.get_parent().get_child('po'); // Load the english translation as a template let template = localedir.get_child('en').get_child('messages.json'); template = JSON.load(template); // let info; const iter = podir.enumerate_children('standard::name', 0, null); while ((info = iter.next_file(null))) { const [lang, ext] = info.get_name().split('.'); // Only process PO files if (ext !== 'po') continue; /** * Convert glibc language codes * * pt_BR => pt-BR * sr@latin => sr-Latn */ let langCode = lang.replace('_', '-'); langCode = langCode.replace('@latin', '-Latn'); print(`Processing ${lang} as ${langCode}`); // Make a new dir and file const jsondir = localedir.get_child(langCode); const jsonfile = jsondir.get_child('messages.json'); GLib.mkdir_with_parents(jsondir.get_path(), 448); // If the translation exists, update the template with its messages let json = JSON.parse(JSON.stringify(template)); if (jsonfile.query_exists(null)) json = Object.assign(json, JSON.load(jsonfile)); // Read the PO file and search the msgid's for our strings let msgid = false; let po = iter.get_child(info).load_contents(null)[1]; po = new TextDecoder().decode(po); for (const line of po.split('\n')) { // If we have a msgid, we're expecting a msgstr if (msgid) { if (MSGSTR_REGEX.test(line)) json[msgid]['message'] = line.match(MSGSTR_REGEX)[1]; msgid = false; // Otherwise set msgid to a mapped message } else if (MSGID_REGEX.test(line)) { msgid = MSGID[line.match(MSGID_REGEX)[1]]; } } // Write the (updated) translation json = `${JSON.stringify(json, null, 4)}\n\n`; jsonfile.replace_contents(json, null, false, 0, null); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/������������������������������0000775�0000000�0000000�00000000000�14607666711�0026023�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/desktop.svg�������������������0000664�0000000�0000000�00000000422�14607666711�0030213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<svg fill="#000000" height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg"> <path d="M0 0h24v24H0z" fill="none"/> <path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z"/> </svg>����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect-128.png�������������0000664�0000000�0000000�00000002500�14607666711�0031021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���€���€���Ã>aË���sBIT|dˆ��� pHYs��;��;̶¡ƒ���tEXtSoftware�www.inkscape.org›î<��½IDATxœíÝ=oEðgf—;Cpä!È•++Jò–,hø4H4A`Þ¿¢BJ$´‡y))\CË…-ErA¶„P¤Ä–Eª ôÏî½ÍzvgvæùUÑ9{÷_Ïs{÷ìÞÉ�ÅF5ÙhýÍõWt‰/�¼ `ÙíHdéÀ6J=LJ¶[à|ñ°b»-µê!J}Ý6ÚöQΟù\üð¬@—ŸÙnd�üwا0ݰݠI�øš®Ë¶4 �E„H¸Üõ~¸ùë»ì-­5ƒ¡Óû|tÓéýñТ,sþürŽh‰R ™Î|1Ð’,ËžhïÐ’, ÿÙ0�­È² Jõàé yÞü=Á�8¦µ†ÒýùµögÒžèCõ“�‡úRý$À¡¾T?‰p¨/ÕOb�éSõ“�GúTý$À¾U?©ŸS¦oÕOêïä˜TýŽŽŽqûNƒƒûøçñãNçY]»f*7*`Û íïîÖ>2Î#ÀU«ßÑÑ1Þ»y ;÷ö:_ü)– ð [][»Zý!pAÕêwûN³³3OÓÌ´dµï 0�0©úÜ÷4Í|¦ö½¾¸€IÕ¯zØ¿ñö×]ƒA®ñÂå§?ƒøí­·þÿ·šð½ ±ú]Z²>‡µ=Zõ˴³ûSÑ @!^õ{n˜7ºÅ�4ÚU?àÒ°Y €B»ê·4Èéf‰d�,…xÕïùoþž`�,…vÕok òæËÈ�Xˆ¥úIaíMàb©~° ˜ªŸÄ�,(¦ê'1� Š©úI Àb«~°€ØªŸÄ�Ìcõ“ÂÚ³�ÅXý$`†X«ŸÄ�Ìkõ“€b­~0EÌÕOb�¦ˆ¹úI À±W?)¬½ DìÕOb�*R¨~P‘Bõ“€ŠªŸÄ�©T?‰R©~p.¥ê'…µÇ¥Tý$�éU?‰@zÕOb�^õ“’@ŠÕOJ>�)V?ÉùÞ¼ù‰ë»LJÕOJþ’®ªŸÄ�¤«ê'1�è²úIMpê| ê´úIM°í| ê´úIö(õÀC÷£¤«ëê'Y?êx<>D©¯Ã¨œ´0Srº®~’Õ#þå§›J᣶†‰ÁáïXýÕOZøÀÅo‡ê'-�.~;|U?in�¸øíñUý¤™àâ·ËWõ“¦€‹ß.ŸÕOš8¿}>«ŸT �¿}¾«ŸT‹áÞÞÞ;>I‰ïê'Õ_”©ýqAr'„ê'Õ`Jlù$!T?©€A¾ô €¿=Ì’„ªŸ41ŠëFá»i?§él¯tmwç©5X‹b«0ÆŒ�Tÿ1EfꙈRo€/Q›y*ª(¶Šg²áU¥ÔÀ=�º‹ºÂ×xÇV×®�Xö=ÇöwwVä þOFGFü™IcðKõ6À1ƒ2ÔÏL>P¹z·zc8§¤"ñ×ññƒ_~é'cÔ(\QÀpþVí1À ~V¹z}ÿîÝ?}ÎBDDDDDDDDDDDDDDDDDDDíùD«°Aøê>Ô����IEND®B`‚������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect-16.png��������������0000664�0000000�0000000�00000000641�14607666711�0030741�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���������óÿa���sBIT|dˆ��� pHYs��b��b8z™Û���tEXtSoftware�www.inkscape.org›î<��IDAT8Å1KBa†ŸóÛÕ*‡À¡¡­A"¤ !mlhŠ ¨ßPôZë74Òšw)½Ž )47«$9ê½ vE»·!z·ï÷ùÎ9ð߀£ãÃ3A®~S ÏË·w×ÀˆÉŠ»÷ 3 Æg€º_§rïe�LÒUíT>NDDð¼[ï#–În_näß:‰ª–~Ÿµ­ƒ/!°²èÒôË™D+¨jäí:ŠÜ@U‘[pÇƒÇ ìÄñkpì¸f‚0èÔª>µª?SÔîöØ<ÙgÞ m©TZÅ /â&yi½¢FH9ã{¤Ó©%Ûh<­‡B6NÐîö<Wè¤Ü ‡ƒeù¡I.¿“3fxúÞ$íÿ]>Ùm?a&J̧����IEND®B`‚�����������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect-22.png��������������0000664�0000000�0000000�00000001101�14607666711�0030726�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���������Ä´l;���sBIT|dˆ��� pHYs�� &�� &Q©©3���tEXtSoftware�www.inkscape.org›î<��¾IDAT8Ý”1O1†ßÏþöÁ%Q:B”-H…"Xå0’¡? ‰©CF$:”Že$¥XÊ€€t×r@/ ¢˜¡Mr§»Jw0Á;Ù¯íÇŸ_Ùž›¨Û(•JSD‰§ÀŒÇ•Jå�¸ç óÕ�£OƒÌ9€±0Æz·¼„™·3ÉDÐJ÷úµï5lo±º}NÖZ#ŸË%33˜úk-+4.QbD$຿à8.ÇÅÕÕ5Zí¶˜]TœTRJl~úŒ}œQý* ÓÙåµ¹µüH0£Ù¼Áäü ”]èù¶fxG?pz°WH‘€ÑeÀ \²´`f뫌‹Þ³H&¤ˆOoH…ýT`É2ðVûb)0ÈaThÏ»Dýøä¿`­4è_¾¾ß^ýõ‡U4ž Ø«îVóÕÝj¢êÝß—(N¬@Ag¢ñ0�ll¬¯2¢ƒÎi"*€o{ûy�ö’qñ”ËåâÑI}Œ©'…€í¿ìàë*—µÇ¹ÑhÚYëÂ�siÀw­–]ÛúˆŸ:üGÜÝþï7Ïc‘LÅ……7lð>:B÷mË}z�»ós »µ����IEND®B`‚���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect-24.png��������������0000664�0000000�0000000�00000001125�14607666711�0030736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���������àw=ø���sBIT|dˆ��� pHYs�� �� �šœ���tEXtSoftware�www.inkscape.org›î<��ÒIDATH‰í”=oÓP†Ÿë{c;JR¡ŠBH-R ª"ÚXØ:PFf„Äo°€¥â/�RÇ"!13˜¯¡Biš,H¤Ž(…Ú$Â6 ±ìƤµaƒgò9çú}ï¹>¾ðŸã‡[­›«À¿#­>y¼v@%òsµzFc!—˜®ëH)ãØq^3pÝùqœ4`¦^ãêµ+¹ ÃDˆø èln1pÝ8Ör©íCJ™ÏBM­DëëÏŽFqªÝéÑìœYXlÞ ¾{ hšF»ÓåÙË'æ–ã¼5š —Jg·Û¯~p^¼)l ¥" BÌêQfNžOÕf«žÛC¡ }!R“™5]iè ÙBRªÄ”¦b¦¥ Aöî¥&(ëéZnƒi£ij¢±ÜJfÏ…�*ÆdgÉÕA·Ûã¾ýàÐf¾?Dº €©K¤6Ù™°mÛ ½÷<?רöÝÏõ]¸pÄÌ~UÙ¶­–z*(­XV%>a(Øõ&GsL¹\6ÔÆÆÛcA¤®çRþ…ïùQ=‡çnÑÿZJÕöv¶‰Â &�Z­K!b%ŠòÝMßö¼ã? nÿvA¤-N¿ A£ÙlPÛŸ—ðåã8ªÿð^Xv™V \����IEND®B`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect-256.png�������������0000664�0000000�0000000�00000005304�14607666711�0031030�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���������\r¨f���sBIT|dˆ��� pHYs��v��v§Âxê���tEXtSoftware�www.inkscape.org›î<�� AIDATxœíÝ_hwÇñïïybÒ¤mªo·›Þu†"±—s°¼¨v³›*E†E/†-n"" :‰‡00éY\ïâ–fkñ¢°?S*dë¥!Ã,ͳs¯”ols¾ÏIžßïùýžçýºÝÙž_Îñùä4ïœ*�����������������������������������������������,.ôÏ<yfÂuåœsò¸ˆ<$"‡CŸˆÈ]Y‘+ÒËf:ÎrÈ‹€©©©‘£ã£/â¾%"Y¨ë é‰ÈôæÆÖ…………í 2�SSS#G޾.…ûJˆë‰»º¹±õDˆòøèøè‹Üü@i}!Ä…¼¿8ó䙉¬'ÞöƒèfÒ;97÷Ú_}^ÄûM™÷Ü7C\h˜¼üœï‹x¿1 )ó}  ‰ )Nû¾Fˆï̸ÐDÞïp$À5€&:êûüÙh1�h1�h1�h1�h1�h1�h±¡º`ùáÅgë>"•e™ Ô}Œ¾¾ÿÌê>B_¼@²ò<úï_Ñc�$çœäY^÷1’Ç� Iy>TÃ_h×< �’”ç|÷¯€ääy.Îñí¿ �’3Äÿ*Ã� )Y–‰ËøŸmUx&‘Ò_µ�$ƒôW=�É ýU@2HÕc�ÒŸ �’@úóƒ@ôHþð¬"z¤?�Dô瀨‘þüb�5ÒŸ_ �¢Eúó@´Hþ1�ˆé/ žaD‰ô€èþÂa�Ò_8 �¢Cú ‡@THa1�ˆ é/,�Ñ ý…dzhþÂc�Ò_=�DôWÞs! eÓßêûwdéÍkróæŠüsmM¶?Ùö|²zMLž*Œ‡Ü‘U)Š+… Í,¿w}yÿ>€Ú•I;;;2óò+²´tMz½^ “%á°ˆœçN8é~gâ˧¦·>\¿pûöíRËÈP;+ýíììÈsÏÿ\ßâæï/“B¾=úÙÏýñøñã#åþ FeÒßÌ˯ÈÊ­¿:Qúœ¸GÇŽ}þ…2e�P++ý­¾G––®:Mƒ¸âüÄääÃÖÃ�Ô¦Lú[ZºÊÛþýÉEÜ9ëA �jS&ýÝ\¾æ0äN[`�P›2éomm-ÀIëAëd@Ô¢ì§þ>1:ÿãOOWu¤è eò…ñþ?ÌÿÝsOõûÇG­kð�µàS¶Ã‡ü?G �‚ãS¶<s2:ìÿ³¼ ŽOýÙÆF†‚|4‚@P|êÏæDäðH˜çˆ@P|êÏvh8—< ó$1�Š¿ðÓv$Àÿþ‹@0ü…Ÿ¶á¡L†‡ÂÝ– �‚!ýÙB¤?@¤?[¨ô§ñŠ ÒŸ-TúÓ�xGú³…L�ïH¶éOc�àéÏ2ýi �¼"ýÙB§?€W¤?[èô§1�ð†ôg«#ýi¼:ð†ôg«#ýi �¼ ýÙêJ�/H¶ºÒŸÆ�À ÒŸ­®ô§1�¨éÏVgúÓê?‡ôg«3ýi �*Eú³Õþ4^)TŠôg«;ýi �*Cú³Åþ4�•!ýÙbH€Êþl1¤?@%H¶XÒŸ×i,ÒŸ-–ô§1�80ÒŸ-¦ô§ñªáÀH¶˜ÒŸÆ�à@H¶ØÒŸÆ�à@H¶ØÒŸÆ�à@H¶ØÒŸÆ�`ßH¶ÓŸïÉ=ÒŸ-Æô§1�ØÒŸ-Öô§ñ b_H¶XÓŸÆ�``¤?[ÌéOc�00ÒŸ-æô§1�éÏsúÓ� „ôg‹=ýiiœÑ ýÙbO€ÒH¶ÒŸÆ«‰ÒH¶ÒŸÆ� ÒŸ-•ô§1�(…ôgK%ýi �J!ýÙRI�éÏ–RúÓÒ;1‚#ýÙRJ€¾H¶ÔÒŸÆ+‹¾H¶ÔÒŸÆ�`O¤?[ŠéOc�°'ÒŸ-Åô§1�ØéÏ–búÓ�ÜéÏ–júÓÒ>=¼!ýÙRM€{þl)§?W÷ ýÙRN€]H¶ÔÓŸÆ�`ÒŸ-õô§1�Ø…ôgK=ýi �þ‡ôgkBúÓšó•àÀH¶&¤?€ˆþÊhJúÓxÅ!"¤¿2š’þ4�¤¿š”þ4�¤¿š”þ4�¤¿š”þ4 åH¶¦¥?­™_J#ýÙš–þ´è¿²_|¾î# Åš˜þ4Þ�}41ýi �°‡¦¦?�öÐÔô§1�Àššþ4�¸&§?­ù_!°MN�üŸ¦§?-Ä�l¸P™¦§?-Ä�|à@%Úþ´°à@%Úþ4ÿÐËfD¤çý:@Úþ4ïÐét–EdÚ÷u€ƒjKúÓ‚|µ›[DäjˆkûÕ–ô§€………íÍ­'œ/‰H7Ä5A´)ýiÁÞï,,,l_šûÃÓ™ôN:q¿‘!"mJš×÷<?ýÙO.:'?òy 4ÛßÿqÇû5Ú–þ4oï�¸ù‘ж¥?ÍË�pó#%mKZåÀÍ”´1ýi•~åÜüHMÓŸVÙ�pó#5mMZ%À͵5ýi�n~¤¨ÍéO;Ð�pó#UmNÚ¾€›)ksúÓö5�ÜüHYÛÓŸ6ð³ÀÍÔµ=ýi �7?RGúÛ­ô�pó£ H»•�n~4éï^æ�pó£)H÷2rãÆgBðôw¯2Xó~ À3ÒßýÙÏHán8àéïþÌp™ü)ÄA�_H{3 èºË!øBúÛ›9�NgY\q=Äa€ª‘þú+÷SW<ëù€¤¿þJ @ç÷¯½)"óžÏTŽô×_ég'wÝïu‹ü´ˆŒ{<0+¿:_÷’V:ŒÎÎ^^u…|CD>õx� ô›—.Í¿."<@`ÿjTgnþ—â伈ìx8€€öõ»‘Ùùßâ¾*"ÿªø<�Ú÷/G¿:÷ê¹ëžê�¬}:bvöòjgnþë’õã—…€ôTúgÏžýbáº_s’="NNˆˆÈ¡*¯v ñÞd7ß}»ï=ίH!j“§>‘#uŸ#E…ÈÆò»oë÷> ¨…|P÷RåÄ~î�DÍI±X÷RUˆ¼a=†@Ô š‘^ÝçHP7sÅo­1�ˆÚò{×—ÅÉtÝçHùõ_ÞygÅz€èm}¸~¡âjÝçHÈ[Ÿ~|÷»eÈß”€è­¯¯wÇÇÆf?32ö€8ù’ðk/]'òR÷ã»O­¬¬ü»Ì¿@DR&&'qçDÜiyHH„›"²êDÅ3eÞö����������������������������������������������������������ÐVÿaßaS(ìu����IEND®B`‚����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect-32.png��������������0000664�0000000�0000000�00000000773�14607666711�0030745�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��� ��� ���szzô���sBIT|dˆ��� pHYs��Ä��Ä•+���tEXtSoftware�www.inkscape.org›î<��xIDATX…í–1NÃ@Dçÿ¿Z‚è¸�HDBÇ ¢ÑQpD"ˆq� :(â”qŠ\¤J¥â ¦!hm¯“M¼yÝÚcÏxýÇ2°ä¿Cæâàp?°³`Ïðõå­>^pêä¢Í f.”MqÙjr`fhdŽ_œ7²ÚBN9ˆXŸËŠ÷�Daù»�"*5Ú“qß+ç�‚F³þ`˜«©T·b€Â^÷£î5€ˆ€ˆÐ ±wöhÕhÅØXðÜ>®ž_r¾r)©ñ€™A<ùv„Po\ª·¨Ì|z àR=P²/\ªWÒá¬ÈS€éžµ’ý0®Þ$´bhe·*`žê%®·¼nÝÌŸ(…­z&é½9ÿ`«žÉïÜÝß¶ˆ’? ó2}ȯž æW>ÌMòªg¢� Š¢#ßæ@~õ2b¢Šã'�›¾ÌßNt3ü:¸S©nw€xÚ<uzÝÏÝEø/Y2ßÿ‚1¡§D²È����IEND®B`‚�����GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect-48.png��������������0000664�0000000�0000000�00000001417�14607666711�0030750�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���0���0���Wù‡���sBIT|dˆ��� pHYs��%��%IR$ð���tEXtSoftware�www.inkscape.org›î<��ŒIDAThí˜ÏkA†Ÿ™Ù즩=y…Õ‹é1-è¡=H-…Òÿ¥Š=xô?c!ŠÕC±?…[0zH{5kÑSkš/©l“M²›ÍÎÜçøí~Ëû~3ó² ¤¤¤¤$‰h-LÏMß– –ÑôtcWN•J¥ª·([ßgú —O<À¨™Öb›)¥cFOxü´YÝ&& LLâSH)±íÎ3ÛXß`ùã§Îýqˆ ƒR]gØ“D !PREúF¢”²|r0 ˆ6}HЀR !"ŽŸ)'–²ø^;äÙó—Ôj‡AÛîÜ_�ö®šÚÝÞ¬&b@J‰’ÍÍ2µÚ!#ãõå²™ŒÅÎÊÒ¨Rî ð"çÑY¯×É?ìÙ#€ëW³()ØYY­Hà ôY[¡dû™1n ßè¼’õß, ?}Û’Ø–¿T£úÎáÓì>þ{” ÙW͘óè KαºcúùëÀ°ÓýÌ10èèôbÄÀ £Ó‹!ƒN/±ˆ#:½Än Žè¼ðýn×V×Y[]- (?~þò­÷ŠN/m+àºîiQQ ^Ú/¶´*UŸw$:½\ØB‹‹‹7‘ÏÀÈ …ùQÙÝãèø÷…Zèôòošâ¿`H¼A£Ó‹„Ë!‚G'@.7t š[h{»ü@ óâ½)&:„Ö· iÀ²œwõ³Ó§n ZdÊpì GCÙÀ='§ö i X,ÏÎ>¾ïjõ ¡ €Ñꃭ÷¡{®{‘/ö¢q7ŸŸ×Z¼î§W =_)—ß$j�cùüœDŒ…ir]¾U¾n½tLºRRRRþþpˆ…¼ý(z����IEND®B`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect-64.png��������������0000664�0000000�0000000�00000001417�14607666711�0030746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���@���@���ªiqÞ���sBIT|dˆ��� pHYs��‡��‡åñe���tEXtSoftware�www.inkscape.org›î<��ŒIDATxœíš?Ó0‡¯Ò‡Ø˜(ŒŒ7·#±±!!1±f@bDTüâ Ú[€¡K%Ö›ø » ÁÄÀÔ®‰ !tf€Fo¡©c;¶ÎÏTÕéû±“_¬H$‰D"qT!]Õ«—/AÑÀiý±ÂŒ€b<~½·ªYhxHÄ>x� ôa kÖ ôœt¨ÎèôŽYÝïõïºìG-„ÈóN­coߺS¯f“ùFÊÚóU›h¤ÖëF#@ʬ"´Í‰H€ýÙ" ¥‘ƒéG$27¿Á B€„»nºSk‰EôMÞãùî Ìç_êlmï¨?g T|Ü»¾xô v_þ/zUî ‚^<ú¾Ìç�€‹×‡k×É3S'?A¾zx `{ƒ W€­èÛìêç9X¶¢O ÂF®¬�[Ñw¼“U>@)ÀVô€ÍNõe¤�[»¾n.!Eõeœ�›»¾7¿Á °µëË3<ûÿðà>ú8A ð}œ øŠ>N0|FßÒyŸÑ>£„�ßÑÇ B€ïèã"ÀoôqZÐFôqZÐFô-¿îú ÊûaÝèã´¾l°nôqôf¦òͺÑÇÑ  ˆA‚IôqVª{òôqŸ÷«:àÓçéÊïM¢óº¯Ã4ú8Kb<`}œR@lƒo}œòšL&7WóH“èã”+€ Æ«y¤IôqÊ*Dz›?~~¿�àœ•ÊŽxó¬°Z¯\£Ñè[žuÏÔ€•¿acÀÍ{'ØÚÞ™ÖÞ^~Øwˆh/ @`åÉtªph÷:J$‰D"‘HDÈ/—YÖ4’À����IEND®B`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/gsconnect.svg�����������������0000664�0000000�0000000�00000004462�14607666711�0030535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32" version="1.1" id="SVGRoot"> <defs id="defs6152" /> <metadata id="metadata6155"> <rdf:RDF> <cc:Work rdf:about=""> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:title></dc:title> </cc:Work> </rdf:RDF> </metadata> <g id="layer1" style="display:inline"> <rect rx="1" y="4" x="3" style="opacity:1;fill:#555753;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.57287949" height="18" width="26" id="rect3749" ry="1" /> <rect y="6" x="5" style="fill:#d3d7cf;fill-opacity:1;stroke:none;stroke-width:0.57592726" height="14" width="22" id="rect3751" /> <path d="M 3,22 1,24 H 31 L 29,22 Z" style="fill:#888a85;fill-opacity:1;stroke:none;stroke-width:0.32499966" id="path3755" /> <path d="m 1,24 h 30 c 0,1 -1,2 -2,2 H 3 C 2,26 1,25 1,24 Z" style="fill:#555753;fill-opacity:1;stroke:none;stroke-width:0.4976353" id="path3753" /> <path d="M 16,20 22,6 h 5 v 14" style="fill:#ffffff;fill-opacity:0.1882353;fill-rule:nonzero;stroke:none;stroke-width:0.63332039" id="path3701-7" /> </g> <g id="layer2" style="display:inline"> <rect rx="1" y="10" x="21" style="display:inline;opacity:1;fill:#2e3436;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.36212718" height="17" width="10" id="rect3749-3" /> <rect y="11" x="22" style="display:inline;fill:#729fcf;fill-opacity:1;stroke:none;stroke-width:0.37558597" height="14" width="8" id="rect3751-6" /> <path d="m 23,25 6,-14 h 1 v 14" style="display:inline;fill:#ffffff;fill-opacity:0.1882353;fill-rule:nonzero;stroke:none;stroke-width:0.37690103" id="path3701" /> </g> </svg> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/laptop.svg��������������������0000664�0000000�0000000�00000000671�14607666711�0030047�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<svg fill="#000000" height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path d="M0 0h24v24H0V0z" id="a"/> </defs> <clipPath id="b"> <use overflow="visible" xlink:href="#a"/> </clipPath> <path clip-path="url(#b)" d="M20 18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/> </svg>�����������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/message.svg�������������������0000664�0000000�0000000�00000000432�14607666711�0030167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<svg fill="#000000" height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg"> <path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"/> <path d="M0 0h24v24H0z" fill="none"/> </svg>��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/open-in-browser.svg�����������0000664�0000000�0000000�00000000441�14607666711�0031571�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<svg fill="#000000" height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg"> <path d="M0 0h24v24H0z" fill="none"/> <path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z"/> </svg>�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/phone.svg���������������������0000664�0000000�0000000�00000000443�14607666711�0027656�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<svg fill="#000000" height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg"> <path d="M16 1H8C6.34 1 5 2.34 5 4v16c0 1.66 1.34 3 3 3h8c1.66 0 3-1.34 3-3V4c0-1.66-1.34-3-3-3zm-2 20h-4v-1h4v1zm3.25-3H6.75V4h10.5v14z"/> <path d="M0 0h24v24H0z" fill="none"/> </svg>�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/images/tablet.svg��������������������0000664�0000000�0000000�00000000721�14607666711�0030017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<svg fill="#000000" height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path d="M0 0h24v24H0z" id="a"/> </defs> <clipPath id="b"> <use overflow="visible" xlink:href="#a"/> </clipPath> <path clip-path="url(#b)" d="M18 0H6C4.34 0 3 1.34 3 3v18c0 1.66 1.34 3 3 3h12c1.66 0 3-1.34 3-3V3c0-1.66-1.34-3-3-3zm-4 22h-4v-1h4v1zm5.25-3H4.75V3h14.5v16z"/> </svg>�����������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/js/����������������������������������0000775�0000000�0000000�00000000000�14607666711�0025172�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/js/background.js���������������������0000664�0000000�0000000�00000026454�14607666711�0027662�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later 'use strict'; const _ABOUT = /^chrome:|^about:/; const _CONTEXTS = [ 'audio', 'page', 'frame', 'link', 'image', // FIREFOX-ONLY: mkwebext.sh will automatically remove this 'tab', 'video', ]; // Suppress errors caused by Mozilla polyfill // TODO: not sure if these are relevant anymore const _MUTE = [ 'Could not establish connection. Receiving end does not exist.', 'The message port closed before a response was received.', ]; /** * State of the extension. */ const State = { connected: false, devices: [], port: null, }; var reconnectDelay = 100; var reconnectTimer = null; var reconnectResetTimer = null; // Simple error logging function // eslint-disable-next-line no-redeclare function logError(error) { if (!_MUTE.includes(error.message)) console.error(error.message); } function toggleAction(tab = null) { try { // Disable on "about:" pages if (_ABOUT.test(tab.url)) browser.browserAction.disable(tab.id); else browser.browserAction.enable(tab.id); } catch (e) { browser.browserAction.disable(); } } /** * Send a message to the native-messaging-host * * @param {Object} message - The message to forward */ // eslint-disable-next-line no-redeclare async function postMessage(message) { try { // console.log(`WebExtension SEND: ${JSON.stringify(message)}`); if (!State.port || !message || !message.type) { console.warn('Missing message parameters'); return; } await State.port.postMessage(message); } catch (e) { logError(e); } } /** * Forward a message from the browserAction popup to the NMH * * @param {Object} message - A message from the NMH to forward * @param {*} sender - A message from the NMH to forward * @param {*} sendResponse - A message from the NMH to forward */ async function onPopupMessage(message, sender, sendResponse) { try { if (sender.url.includes('/popup.html')) await postMessage(message); } catch (e) { logError(e); } } /** * Forward a message from the NMH to the browserAction popup * * @param {Object} message - A message from the NMH to forward */ async function forwardPortMessage(message) { try { await browser.runtime.sendMessage(message); } catch (e) { logError(e); } } /** * Context Menu Item Callback * * @param {menus.OnClickData} info - Information about the item and context * @param {tabs.Tab} tab - The details of the tab where the click took place */ async function onContextItem(info, tab) { try { const [id, action] = info.menuItemId.split(':'); await postMessage({ type: 'share', data: { device: id, url: info.linkUrl || info.srcUrl || info.frameUrl || info.pageUrl, action: action, }, }); } catch (e) { logError(e); } } /** * Populate the context menu * * @param {tabs.Tab} tab - The current tab */ async function createContextMenu(tab) { try { // Clear context menu await browser.contextMenus.removeAll(); // Bail on "about:" page or no devices if (_ABOUT.test(tab.url) || State.devices.length === 0) return; // Multiple devices; we'll have at least one submenu level if (State.devices.length > 1) { await browser.contextMenus.create({ id: 'contextMenuMultipleDevices', title: browser.i18n.getMessage('contextMenuMultipleDevices'), contexts: _CONTEXTS, }); for (const device of State.devices) { if (device.share && device.telephony) { await browser.contextMenus.create({ id: device.id, title: device.name, parentId: 'contextMenuMultipleDevices', }); await browser.contextMenus.create({ id: `${device.id}:share`, title: browser.i18n.getMessage('shareMessage'), parentId: device.id, contexts: _CONTEXTS, onclick: onContextItem, }); await browser.contextMenus.create({ id: `${device.id}:telephony`, title: browser.i18n.getMessage('smsMessage'), parentId: device.id, contexts: _CONTEXTS, onclick: onContextItem, }); } else { let pluginAction, pluginName; if (device.share) { pluginAction = 'share'; pluginName = browser.i18n.getMessage('shareMessage'); } else { pluginAction = 'telephony'; pluginName = browser.i18n.getMessage('smsMessage'); } await browser.contextMenus.create({ id: `${device.id}:${pluginAction}`, title: browser.i18n.getMessage( 'contextMenuSinglePlugin', [device.name, pluginName] ), parentId: 'contextMenuMultipleDevices', contexts: _CONTEXTS, onclick: onContextItem, }); } } // One device; we'll create a top level menu } else { const device = State.devices[0]; if (device.share && device.telephony) { await browser.contextMenus.create({ id: device.id, title: device.name, contexts: _CONTEXTS, }); await browser.contextMenus.create({ id: `${device.id}:share`, title: browser.i18n.getMessage('shareMessage'), parentId: device.id, contexts: _CONTEXTS, onclick: onContextItem, }); await browser.contextMenus.create({ id: `${device.id}:telephony`, title: browser.i18n.getMessage('smsMessage'), parentId: device.id, contexts: _CONTEXTS, onclick: onContextItem, }); } else { let pluginAction, pluginName; if (device.share) { pluginAction = 'share'; pluginName = browser.i18n.getMessage('shareMessage'); } else { pluginAction = 'telephony'; pluginName = browser.i18n.getMessage('smsMessage'); } await browser.contextMenus.create({ id: `${device.id}:${pluginAction}`, title: browser.i18n.getMessage( 'contextMenuSinglePlugin', [device.name, pluginName] ), contexts: _CONTEXTS, onclick: onContextItem, }); } } } catch (e) { logError(e); } } /** * Message Handling * * @param {Object} message - A message received from the NMH */ async function onPortMessage(message) { try { // console.log(`WebExtension RECV: ${JSON.stringify(message)}`); // The native-messaging-host's connection to the service has changed if (message.type === 'connected') { State.connected = message.data; if (State.connected) postMessage({type: 'devices'}); else State.devices = []; // We're being sent a list of devices (so the NMH must be connected) } else if (message.type === 'devices') { State.connected = true; State.devices = message.data; } // Forward the message to popup.html forwardPortMessage(message); // const tabs = await browser.tabs.query({ active: true, currentWindow: true, }); createContextMenu(tabs[0]); } catch (e) { logError(e); } } /** * Callback for disconnection from the native-messaging-host * * @param {object} port - The port that is now invalid */ async function onDisconnect(port) { try { State.connected = false; State.port = null; browser.browserAction.setBadgeText({text: '\u26D4'}); browser.browserAction.setBadgeBackgroundColor({color: [198, 40, 40, 255]}); forwardPortMessage({type: 'connected', data: false}); // Clear context menu await browser.contextMenus.removeAll(); // Disconnected, cancel back-off reset if (typeof reconnectResetTimer === 'number') { window.clearTimeout(reconnectResetTimer); reconnectResetTimer = null; } // Don't queue more than one reconnect if (typeof reconnectTimer === 'number') { window.clearTimeout(reconnectTimer); reconnectTimer = null; } // Log disconnection if (browser.runtime.lastError) { const message = browser.runtime.lastError.message; console.warn(`Disconnected: ${message}`); } // Exponential back-off on reconnect reconnectTimer = window.setTimeout(connect, reconnectDelay); reconnectDelay *= 2; } catch (e) { logError(e); } } /** * Start and/or connect to the native-messaging-host */ async function connect() { try { State.port = browser.runtime.connectNative('org.gnome.shell.extensions.gsconnect'); // Clear the badge and tell the popup we're disconnected browser.browserAction.setBadgeText({text: ''}); browser.browserAction.setBadgeBackgroundColor({color: [0, 0, 0, 0]}); // Reset the back-off delay if we stay connected reconnectResetTimer = window.setTimeout(() => { reconnectDelay = 100; }, reconnectDelay * 0.9); // Start listening and request a list of available devices State.port.onDisconnect.addListener(onDisconnect); State.port.onMessage.addListener(onPortMessage); await State.port.postMessage({type: 'devices'}); } catch (e) { logError(e); } } // Forward messages from the browserAction popup browser.runtime.onMessage.addListener(onPopupMessage); // Keep browserAction up to date browser.tabs.onActivated.addListener((info) => { browser.tabs.get(info.tabId).then(toggleAction); }); browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.url) toggleAction(tab); }); // Keep contextMenu up to date browser.tabs.onActivated.addListener((info) => { browser.tabs.get(info.tabId).then(createContextMenu); }); browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.url) createContextMenu(tab); }); /** * Startup: set initial state of the browserAction and try to connect */ toggleAction(); connect(); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/js/browser-polyfill.min.js�����������0000664�0000000�0000000�00000023722�14607666711�0031633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: Mozilla Foundation // // SPDX-License-Identifier: MPL-2.0 (function(a,b){if("function"==typeof define&&define.amd)define("webextension-polyfill",["module"],b);else if("undefined"!=typeof exports)b(module);else{var c={exports:{}};b(c),a.browser=c.exports}})("undefined"==typeof globalThis?"undefined"==typeof self?this:self:globalThis,function(a){"use strict";if("undefined"==typeof browser||Object.getPrototypeOf(browser)!==Object.prototype){if("object"!=typeof chrome||!chrome||!chrome.runtime||!chrome.runtime.id)throw new Error("This script should only be loaded in a browser extension.");a.exports=(a=>{const b={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(b).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class c extends WeakMap{constructor(a,b=void 0){super(b),this.createItem=a}get(a){return this.has(a)||this.set(a,this.createItem(a)),super.get(a)}}const d=a=>a&&"object"==typeof a&&"function"==typeof a.then,e=(b,c)=>(...d)=>{a.runtime.lastError?b.reject(a.runtime.lastError):c.singleCallbackArg||1>=d.length&&!1!==c.singleCallbackArg?b.resolve(d[0]):b.resolve(d)},f=a=>1==a?"argument":"arguments",g=(a,b)=>function(c,...d){if(d.length<b.minArgs)throw new Error(`Expected at least ${b.minArgs} ${f(b.minArgs)} for ${a}(), got ${d.length}`);if(d.length>b.maxArgs)throw new Error(`Expected at most ${b.maxArgs} ${f(b.maxArgs)} for ${a}(), got ${d.length}`);return new Promise((f,g)=>{if(b.fallbackToNoCallback)try{c[a](...d,e({resolve:f,reject:g},b))}catch(e){console.warn(`${a} API method doesn't seem to support the callback parameter, `+"falling back to call it without a callback: ",e),c[a](...d),b.fallbackToNoCallback=!1,b.noCallback=!0,f()}else b.noCallback?(c[a](...d),f()):c[a](...d,e({resolve:f,reject:g},b))})},h=(a,b,c)=>new Proxy(b,{apply(b,d,e){return c.call(d,a,...e)}});let i=Function.call.bind(Object.prototype.hasOwnProperty);const j=(a,b={},c={})=>{let d=Object.create(null),e={has(b,c){return c in a||c in d},get(e,f,k){if(f in d)return d[f];if(!(f in a))return;let l=a[f];if("function"==typeof l){if("function"==typeof b[f])l=h(a,a[f],b[f]);else if(i(c,f)){let b=g(f,c[f]);l=h(a,a[f],b)}else l=l.bind(a);}else if("object"==typeof l&&null!==l&&(i(b,f)||i(c,f)))l=j(l,b[f],c[f]);else if(i(c,"*"))l=j(l,b[f],c["*"]);else return Object.defineProperty(d,f,{configurable:!0,enumerable:!0,get(){return a[f]},set(b){a[f]=b}}),l;return d[f]=l,l},set(b,c,e,f){return c in d?d[c]=e:a[c]=e,!0},defineProperty(a,b,c){return Reflect.defineProperty(d,b,c)},deleteProperty(a,b){return Reflect.deleteProperty(d,b)}},f=Object.create(a);return new Proxy(f,e)},k=a=>({addListener(b,c,...d){b.addListener(a.get(c),...d)},hasListener(b,c){return b.hasListener(a.get(c))},removeListener(b,c){b.removeListener(a.get(c))}});let l=!1;const m=new c(a=>"function"==typeof a?function(b,c,e){let f,g,h=!1,i=new Promise(a=>{f=function(b){l||(console.warn("Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)",new Error().stack),l=!0),h=!0,a(b)}});try{g=a(b,c,f)}catch(a){g=Promise.reject(a)}const j=!0!==g&&d(g);if(!0!==g&&!j&&!h)return!1;const k=a=>{a.then(a=>{e(a)},a=>{let b;b=a&&(a instanceof Error||"string"==typeof a.message)?a.message:"An unexpected error occurred",e({__mozWebExtensionPolyfillReject__:!0,message:b})}).catch(a=>{console.error("Failed to send onMessage rejected reply",a)})};return j?k(g):k(i),!0}:a),n=({reject:b,resolve:c},d)=>{a.runtime.lastError?a.runtime.lastError.message==="The message port closed before a response was received."?c():b(a.runtime.lastError):d&&d.__mozWebExtensionPolyfillReject__?b(new Error(d.message)):c(d)},o=(a,b,c,...d)=>{if(d.length<b.minArgs)throw new Error(`Expected at least ${b.minArgs} ${f(b.minArgs)} for ${a}(), got ${d.length}`);if(d.length>b.maxArgs)throw new Error(`Expected at most ${b.maxArgs} ${f(b.maxArgs)} for ${a}(), got ${d.length}`);return new Promise((a,b)=>{const e=n.bind(null,{resolve:a,reject:b});d.push(e),c.sendMessage(...d)})},p={runtime:{onMessage:k(m),onMessageExternal:k(m),sendMessage:o.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:o.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},q={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return b.privacy={network:{"*":q},services:{"*":q},websites:{"*":q}},j(a,p,b)})(chrome)}else a.exports=browser}); //# sourceMappingURL=browser-polyfill.min.js.map // webextension-polyfill v.0.6.0 (https://github.com/mozilla/webextension-polyfill) /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ����������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/js/browser-polyfill.min.js.map�������0000664�0000000�0000000�00000151430�14607666711�0032405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{"version":3,"sources":["browser-polyfill.js"],"names":["Object","extensionAPIs","apiMetadata","constructor","items","get","isThenable","value","makeCallback","promise","metadata","callbackArgs","pluralizeArguments","numArgs","wrapAsyncFunction","args","minArgs","name","length","maxArgs","target","resolve","reject","console","wrapMethod","apply","wrapper","hasOwnProperty","Function","wrapObject","wrappers","cache","handlers","has","prop","configurable","enumerable","set","defineProperty","Reflect","deleteProperty","proxyTarget","wrapEvent","wrapperMap","addListener","hasListener","removeListener","loggedSendResponseDeprecationWarning","onMessageWrappers","listener","didCallSendResponse","sendResponsePromise","wrappedSendResponse","result","Promise","isResultThenable","sendPromisedResult","msg","sendResponse","error","message","__mozWebExtensionPolyfillReject__","err","wrappedSendMessageCallback","reply","wrappedSendMessage","wrappedCb","apiNamespaceObj","staticWrappers","runtime","onMessage","onMessageExternal","sendMessage","tabs","settingMetadata","clear","network","services","websites","chrome","module","wrapAPIs"],"mappings":"gSAMA,aAEA,GAAI,WAAA,QAAA,CAAA,OAAA,EAAkCA,MAAM,CAANA,cAAAA,CAAAA,OAAAA,IAAmCA,MAAM,CAA/E,SAAA,CAA2F,CAkoCzF,GAAI,QAAA,QAAA,CAAA,MAAA,EAA6B,CAA7B,MAAA,EAAwC,CAAC+E,MAAM,CAA/C,OAAA,EAA2D,CAACA,MAAM,CAANA,OAAAA,CAAhE,EAAA,CACE,KAAM,IAAA,CAAA,KAAA,CAAN,2DAAM,CAAN,CAKFC,CAAM,CAANA,OAAAA,CAAiBC,CA/nCAhF,CAAa,EAAI,CAIhC,KAAMC,CAAAA,CAAW,CAAG,CAClB,OAAU,CACR,MAAS,CACP,QADO,CAAA,CAEP,QAAW,CAFJ,CADD,CAKR,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CALJ,CASR,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CATC,CAaR,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAbF,CADQ,CAmBlB,UAAa,CACX,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CADC,CAKX,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CALI,CASX,YAAe,CACb,QADa,CAAA,CAEb,QAAW,CAFE,CATJ,CAaX,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CAbF,CAiBX,WAAc,CACZ,QADY,CAAA,CAEZ,QAAW,CAFC,CAjBH,CAqBX,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CArBA,CAyBX,KAAQ,CACN,QADM,CAAA,CAEN,QAAW,CAFL,CAzBG,CA6BX,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CA7BC,CAiCX,WAAc,CACZ,QADY,CAAA,CAEZ,QAAW,CAFC,CAjCH,CAqCX,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CArCC,CAyCX,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAzCC,CAnBK,CAiElB,cAAiB,CACf,QAAW,CACT,QADS,CAAA,CAET,QAFS,CAAA,CAGT,uBAHS,CADI,CAMf,OAAU,CACR,QADQ,CAAA,CAER,QAFQ,CAAA,CAGR,uBAHQ,CANK,CAWf,wBAA2B,CACzB,QADyB,CAAA,CAEzB,QAAW,CAFc,CAXZ,CAef,aAAgB,CACd,QADc,CAAA,CAEd,QAAW,CAFG,CAfD,CAmBf,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CAnBG,CAuBf,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CAvBG,CA2Bf,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CA3BE,CA+Bf,wBAA2B,CACzB,QADyB,CAAA,CAEzB,QAFyB,CAAA,CAGzB,uBAHyB,CA/BZ,CAoCf,aAAgB,CACd,QADc,CAAA,CAEd,QAFc,CAAA,CAGd,uBAHc,CApCD,CAyCf,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CAzCI,CA6Cf,SAAY,CACV,QADU,CAAA,CAEV,QAFU,CAAA,CAGV,uBAHU,CA7CG,CAkDf,SAAY,CACV,QADU,CAAA,CAEV,QAFU,CAAA,CAGV,uBAHU,CAlDG,CAjEC,CAyHlB,aAAgB,CACd,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CADI,CAKd,YAAe,CACb,QADa,CAAA,CAEb,QAAW,CAFE,CALD,CASd,cAAiB,CACf,QADe,CAAA,CAEf,QAAW,CAFI,CATH,CAad,gBAAmB,CACjB,QADiB,CAAA,CAEjB,QAAW,CAFM,CAbL,CAiBd,eAAkB,CAChB,QADgB,CAAA,CAEhB,QAAW,CAFK,CAjBJ,CAqBd,cAAiB,CACf,QADe,CAAA,CAEf,QAAW,CAFI,CArBH,CAyBd,mBAAsB,CACpB,QADoB,CAAA,CAEpB,QAAW,CAFS,CAzBR,CA6Bd,gBAAmB,CACjB,QADiB,CAAA,CAEjB,QAAW,CAFM,CA7BL,CAiCd,iBAAoB,CAClB,QADkB,CAAA,CAElB,QAAW,CAFO,CAjCN,CAqCd,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CArCE,CAzHE,CAmKlB,SAAY,CACV,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CADA,CAnKM,CAyKlB,aAAgB,CACd,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CADI,CAKd,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CALC,CASd,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CATI,CAzKE,CAuLlB,QAAW,CACT,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CADE,CAKT,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CALD,CAST,mBAAsB,CACpB,QADoB,CAAA,CAEpB,QAAW,CAFS,CATb,CAaT,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAbD,CAiBT,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CAjBE,CAvLO,CA6MlB,SAAY,CACV,gBAAmB,CACjB,KAAQ,CACN,QADM,CAAA,CAEN,QAFM,CAAA,CAGN,oBAHM,CADS,CADT,CAQV,OAAU,CACR,OAAU,CACR,QADQ,CAAA,CAER,QAFQ,CAAA,CAGR,oBAHQ,CADF,CARA,CA7MM,CA6NlB,UAAa,CACX,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CADC,CAKX,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CALD,CASX,MAAS,CACP,QADO,CAAA,CAEP,QAAW,CAFJ,CATE,CAaX,YAAe,CACb,QADa,CAAA,CAEb,QAAW,CAFE,CAbJ,CAiBX,KAAQ,CACN,QADM,CAAA,CAEN,QAFM,CAAA,CAGN,uBAHM,CAjBG,CAsBX,MAAS,CACP,QADO,CAAA,CAEP,QAAW,CAFJ,CAtBE,CA0BX,WAAc,CACZ,QADY,CAAA,CAEZ,QAAW,CAFC,CA1BH,CA8BX,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CA9BC,CAkCX,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAlCC,CAsCX,KAAQ,CACN,QADM,CAAA,CAEN,QAFM,CAAA,CAGN,uBAHM,CAtCG,CA7NK,CAyQlB,UAAa,CACX,0BAA6B,CAC3B,QAD2B,CAAA,CAE3B,QAAW,CAFgB,CADlB,CAKX,yBAA4B,CAC1B,QAD0B,CAAA,CAE1B,QAAW,CAFe,CALjB,CAzQK,CAmRlB,QAAW,CACT,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CADD,CAKT,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CALJ,CAST,YAAe,CACb,QADa,CAAA,CAEb,QAAW,CAFE,CATN,CAaT,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CAbJ,CAiBT,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CAjBJ,CAqBT,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CArBD,CAnRO,CA6SlB,KAAQ,CACN,eAAkB,CAChB,QADgB,CAAA,CAEhB,QAAW,CAFK,CADZ,CAKN,mBAAsB,CACpB,QADoB,CAAA,CAEpB,QAAW,CAFS,CALhB,CA7SU,CAuTlB,SAAY,CACV,kBAAqB,CACnB,QADmB,CAAA,CAEnB,QAAW,CAFQ,CADX,CAvTM,CA6TlB,KAAQ,CACN,WAAc,CACZ,QADY,CAAA,CAEZ,QAAW,CAFC,CADR,CA7TU,CAmUlB,WAAc,CACZ,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CADK,CAKZ,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CALE,CASZ,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CATC,CAaZ,WAAc,CACZ,QADY,CAAA,CAEZ,QAAW,CAFC,CAbF,CAiBZ,cAAiB,CACf,QADe,CAAA,CAEf,QAAW,CAFI,CAjBL,CAnUI,CAyVlB,cAAiB,CACf,MAAS,CACP,QADO,CAAA,CAEP,QAAW,CAFJ,CADM,CAKf,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CALK,CASf,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CATK,CAaf,mBAAsB,CACpB,QADoB,CAAA,CAEpB,QAAW,CAFS,CAbP,CAiBf,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAjBK,CAzVC,CA+WlB,WAAc,CACZ,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CADA,CAKZ,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CALA,CASZ,KAAQ,CACN,QADM,CAAA,CAEN,QAFM,CAAA,CAGN,uBAHM,CATI,CAcZ,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CAdC,CAkBZ,SAAY,CACV,QADU,CAAA,CAEV,QAFU,CAAA,CAGV,uBAHU,CAlBA,CAuBZ,SAAY,CACV,QADU,CAAA,CAEV,QAFU,CAAA,CAGV,uBAHU,CAvBA,CA4BZ,KAAQ,CACN,QADM,CAAA,CAEN,QAFM,CAAA,CAGN,uBAHM,CA5BI,CA/WI,CAiZlB,YAAe,CACb,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CADC,CAKb,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CALG,CASb,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CATG,CAab,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CAbE,CAjZG,CAmalB,QAAW,CACT,kBAAqB,CACnB,QADmB,CAAA,CAEnB,QAAW,CAFQ,CADZ,CAKT,gBAAmB,CACjB,QADiB,CAAA,CAEjB,QAAW,CAFM,CALV,CAST,gBAAmB,CACjB,QADiB,CAAA,CAEjB,QAAW,CAFM,CATV,CAaT,mBAAsB,CACpB,QADoB,CAAA,CAEpB,QAAW,CAFS,CAbb,CAiBT,YAAe,CACb,QADa,CAAA,CAEb,QAAW,CAFE,CAjBN,CAqBT,kBAAqB,CACnB,QADmB,CAAA,CAEnB,QAAW,CAFQ,CArBZ,CAyBT,gBAAmB,CACjB,QADiB,CAAA,CAEjB,QAAW,CAFM,CAzBV,CAnaO,CAiclB,SAAY,CACV,WAAc,CACZ,QADY,CAAA,CAEZ,QAAW,CAFC,CADJ,CAKV,kBAAqB,CACnB,QADmB,CAAA,CAEnB,QAAW,CAFQ,CALX,CASV,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CATD,CAjcM,CA+clB,QAAW,CACT,MAAS,CACP,MAAS,CACP,QADO,CAAA,CAEP,QAAW,CAFJ,CADF,CAKP,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CALA,CASP,cAAiB,CACf,QADe,CAAA,CAEf,QAAW,CAFI,CATV,CAaP,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAbH,CAiBP,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CAjBA,CADA,CAuBT,QAAW,CACT,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CADE,CAKT,cAAiB,CACf,QADe,CAAA,CAEf,QAAW,CAFI,CALR,CAvBF,CAiCT,KAAQ,CACN,MAAS,CACP,QADO,CAAA,CAEP,QAAW,CAFJ,CADH,CAKN,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CALD,CASN,cAAiB,CACf,QADe,CAAA,CAEf,QAAW,CAFI,CATX,CAaN,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAbJ,CAiBN,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CAjBD,CAjCC,CA/cO,CAugBlB,KAAQ,CACN,kBAAqB,CACnB,QADmB,CAAA,CAEnB,QAAW,CAFQ,CADf,CAKN,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CALJ,CASN,eAAkB,CAChB,QADgB,CAAA,CAEhB,QAAW,CAFK,CATZ,CAaN,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CAbL,CAiBN,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CAjBP,CAqBN,cAAiB,CACf,QADe,CAAA,CAEf,QAAW,CAFI,CArBX,CAyBN,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CAzBD,CA6BN,WAAc,CACZ,QADY,CAAA,CAEZ,QAAW,CAFC,CA7BR,CAiCN,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CAjCL,CAqCN,gBAAmB,CACjB,QADiB,CAAA,CAEjB,QAAW,CAFM,CArCb,CAyCN,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CAzCP,CA6CN,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CA7CP,CAiDN,KAAQ,CACN,QADM,CAAA,CAEN,QAAW,CAFL,CAjDF,CAqDN,MAAS,CACP,QADO,CAAA,CAEP,QAAW,CAFJ,CArDH,CAyDN,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAzDJ,CA6DN,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CA7DJ,CAiEN,UAAa,CACX,QADW,CAAA,CAEX,QAAW,CAFA,CAjEP,CAqEN,YAAe,CACb,QADa,CAAA,CAEb,QAAW,CAFE,CArET,CAyEN,QAAW,CACT,QADS,CAAA,CAET,QAAW,CAFF,CAzEL,CA6EN,gBAAmB,CACjB,QADiB,CAAA,CAEjB,QAAW,CAFM,CA7Eb,CAiFN,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAjFJ,CAvgBU,CA6lBlB,SAAY,CACV,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CADG,CA7lBM,CAmmBlB,cAAiB,CACf,aAAgB,CACd,QADc,CAAA,CAEd,QAAW,CAFG,CADD,CAKf,SAAY,CACV,QADU,CAAA,CAEV,QAAW,CAFD,CALG,CAnmBC,CA6mBlB,WAAc,CACZ,uBAA0B,CACxB,QADwB,CAAA,CAExB,QAAW,CAFa,CADd,CA7mBI,CAmnBlB,QAAW,CACT,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CADD,CAKT,IAAO,CACL,QADK,CAAA,CAEL,QAAW,CAFN,CALE,CAST,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CATD,CAaT,WAAc,CACZ,QADY,CAAA,CAEZ,QAAW,CAFC,CAbL,CAiBT,eAAkB,CAChB,QADgB,CAAA,CAEhB,QAAW,CAFK,CAjBT,CAqBT,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CArBD,CAyBT,OAAU,CACR,QADQ,CAAA,CAER,QAAW,CAFH,CAzBD,CAnnBO,CAApB,CAmpBA,GAAA,CAAIF,GAAAA,MAAM,CAANA,IAAAA,CAAAA,CAAAA,EAAAA,MAAJ,CACE,KAAM,IAAA,CAAA,KAAA,CAAN,6DAAM,CAAN,CAaF,KAAA,CAAA,CAAA,QAAA,CAAA,OAAqC,CACnCG,WAAW,CAAA,CAAA,CAAaC,CAAb,OAAA,CAAgC,CACzC,MAAA,CAAA,CADyC,CAEzC,KAAA,UAAA,CAAA,CACD,CAEDC,GAAG,CAAA,CAAA,CAAM,CAKP,MAJK,MAAA,GAAA,CAAL,CAAK,CAIL,EAHE,KAAA,GAAA,CAAA,CAAA,CAAc,KAAA,UAAA,CAAd,CAAc,CAAd,CAGF,CAAO,MAAA,GAAA,CAAP,CAAO,CACR,CAZkC,CArqBL,KA2rB1BC,CAAAA,CAAU,CAAGC,CAAK,EACfA,CAAK,EAALA,QAAS,QAAA,CAAA,CAATA,EAAP,UAA6C,QAAOA,CAAAA,CAAK,CAAZ,IA5rBf,CA0tB1BC,CAAY,CAAG,CAAA,CAAA,CAAA,CAAA,GACZ,CAAC,GAAD,CAAA,GAAqB,CACtBP,CAAa,CAAbA,OAAAA,CAAJ,SAD0B,CAExBQ,CAAO,CAAPA,MAAAA,CAAeR,CAAa,CAAbA,OAAAA,CAAfQ,SAAAA,CAFwB,CAGfC,CAAQ,CAARA,iBAAAA,EACCC,CAAAA,EAAAA,CAAY,CAAZA,MAAAA,EAA4BD,KAAAA,CAAQ,CADzC,iBAHmB,CAKxBD,CAAO,CAAPA,OAAAA,CAAgBE,CAAY,CAA5BF,CAA4B,CAA5BA,CALwB,CAOxBA,CAAO,CAAPA,OAAAA,CAAAA,CAAAA,CAPJ,CA3tB8B,CAuuB1BG,CAAkB,CAAIC,CAAD,EAAaA,CAAAA,EAAAA,CAAO,CAAPA,UAAO,CAA/C,WAvuBgC,CA+vB1BC,CAAiB,CAAG,CAAA,CAAA,CAAA,CAAA,GACjB,SAAA,CAAA,CAAsC,GAAtC,CAAA,CAA+C,CACpD,GAAIC,CAAI,CAAJA,MAAAA,CAAcL,CAAQ,CAA1B,OAAA,CACE,KAAM,IAAA,CAAA,KAAA,CAAW,qBAAoBA,CAAQ,CAACM,OAAQ,IAAGJ,CAAkB,CAACF,CAAQ,CAAT,OAAA,CAAmB,QAAOO,CAAK,WAAUF,CAAI,CAACG,MAAzH,EAAM,CAAN,CAGF,GAAIH,CAAI,CAAJA,MAAAA,CAAcL,CAAQ,CAA1B,OAAA,CACE,KAAM,IAAA,CAAA,KAAA,CAAW,oBAAmBA,CAAQ,CAACS,OAAQ,IAAGP,CAAkB,CAACF,CAAQ,CAAT,OAAA,CAAmB,QAAOO,CAAK,WAAUF,CAAI,CAACG,MAAxH,EAAM,CAAN,CAGF,MAAO,IAAA,CAAA,OAAA,CAAY,CAAA,CAAA,CAAA,CAAA,GAAqB,CACtC,GAAIR,CAAQ,CAAZ,oBAAA,CAIE,GAAI,CACFU,CAAM,CAANA,CAAM,CAANA,CAAa,GAAbA,CAAAA,CAAsBZ,CAAY,CAAC,CAACa,OAAD,CAACA,CAAD,CAAUC,MAAAA,CAAAA,CAAV,CAAD,CAAlCF,CAAkC,CAAlCA,CADF,CAEE,MAAA,CAAA,CAAgB,CAChBG,OAAO,CAAPA,IAAAA,CAAc,GAAEN,CAAH,8DAAC,CAAdM,8CAAAA,CAAAA,CAAAA,CADgB,CAIhBH,CAAM,CAANA,CAAM,CAANA,CAAa,GAJG,CAIhBA,CAJgB,CAQhBV,CAAQ,CAARA,oBAAAA,GARgB,CAShBA,CAAQ,CAARA,UAAAA,GATgB,CAWhBW,CAAO,EACR,CAlBH,IAmBWX,CAAAA,CAAQ,CAAZ,UAnBP,EAoBEU,CAAM,CAANA,CAAM,CAANA,CAAa,GAAbA,CAAAA,CApBF,CAqBEC,CAAO,EArBT,EAuBED,CAAM,CAANA,CAAM,CAANA,CAAa,GAAbA,CAAAA,CAAsBZ,CAAY,CAAC,CAACa,OAAD,CAACA,CAAD,CAAUC,MAAAA,CAAAA,CAAV,CAAD,CAAlCF,CAAkC,CAAlCA,CAxBJ,CAAO,CATT,CAhwB8B,CA0zB1BI,CAAU,CAAG,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GACV,GAAA,CAAA,KAAA,CAAA,CAAA,CAAkB,CACvBC,KAAK,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAA8B,CACjC,MAAOC,CAAAA,CAAO,CAAPA,IAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAA8B,GAArC,CAAOA,CACR,CAHsB,CAAlB,CA3zBuB,CAk0BhC,GAAIC,CAAAA,CAAc,CAAGC,QAAQ,CAARA,IAAAA,CAAAA,IAAAA,CAAmB5B,MAAM,CAANA,SAAAA,CAAxC,cAAqB4B,CAArB,CAl0BgC,KA21B1BC,CAAAA,CAAU,CAAG,CAAA,CAAA,CAASC,CAAQ,CAAjB,EAAA,CAAwBpB,CAAQ,CAAhC,EAAA,GAA0C,IACvDqB,CAAAA,CAAK,CAAG/B,MAAM,CAANA,MAAAA,CAAZ,IAAYA,CAD+C,CAEvDgC,CAAQ,CAAG,CACbC,GAAG,CAAA,CAAA,CAAA,CAAA,CAAoB,CACrB,MAAOC,CAAAA,CAAI,GAAJA,CAAAA,CAAAA,EAAkBA,CAAI,GAA7B,CAAA,CAFW,CAAA,CAKb7B,GAAG,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAA8B,CAC/B,GAAI6B,CAAI,GAAR,CAAA,CAAA,CACE,MAAOH,CAAAA,CAAK,CAAZ,CAAY,CAAZ,CAGF,GAAI,EAAEG,CAAI,GAAV,CAAA,CAAI,CAAJ,CACE,OAGF,GAAI3B,CAAAA,CAAK,CAAGa,CAAM,CAAlB,CAAkB,CAAlB,CAEA,GAAA,UAAI,QAAA,CAAA,CAAJ,EAIE,GAAA,UAAI,QAAOU,CAAAA,CAAQ,CAAf,CAAe,CAAnB,CAEEvB,CAAK,CAAGiB,CAAU,CAAA,CAAA,CAASJ,CAAM,CAAf,CAAe,CAAf,CAAuBU,CAAQ,CAAjDvB,CAAiD,CAA/B,CAFpB,KAGO,IAAIoB,CAAc,CAAA,CAAA,CAAlB,CAAkB,CAAlB,CAAoC,CAGzC,GAAID,CAAAA,CAAO,CAAGZ,CAAiB,CAAA,CAAA,CAAOJ,CAAQ,CAA9C,CAA8C,CAAf,CAA/B,CACAH,CAAK,CAAGiB,CAAU,CAAA,CAAA,CAASJ,CAAM,CAAf,CAAe,CAAf,CAAlBb,CAAkB,CAJb,CAAA,IAQLA,CAAAA,CAAK,CAAGA,CAAK,CAALA,IAAAA,CAARA,CAAQA,CARH,CAPT,KAiBO,IAAI,QAAA,QAAA,CAAA,CAAA,EAAA,IAA6BA,GAAAA,CAA7B,GACCoB,CAAc,CAAA,CAAA,CAAdA,CAAc,CAAdA,EACAA,CAAc,CAAA,CAAA,CAFnB,CAEmB,CAFf,CAAJ,CAMLpB,CAAK,CAAGsB,CAAU,CAAA,CAAA,CAAQC,CAAQ,CAAhB,CAAgB,CAAhB,CAAwBpB,CAAQ,CAAlDH,CAAkD,CAAhC,CANb,KAOA,IAAIoB,CAAc,CAAA,CAAA,CAAlB,GAAkB,CAAlB,CAELpB,CAAK,CAAGsB,CAAU,CAAA,CAAA,CAAQC,CAAQ,CAAhB,CAAgB,CAAhB,CAAwBpB,CAAQ,CAAlDH,GAAkD,CAAhC,CAFb,KAiBL,OAXAP,CAAAA,MAAM,CAANA,cAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAmC,CACjCmC,YADiC,GAAA,CAEjCC,UAFiC,GAAA,CAGjC/B,GAAG,EAAG,CACJ,MAAOe,CAAAA,CAAM,CAAb,CAAa,CAJkB,CAAA,CAMjCiB,GAAG,CAAA,CAAA,CAAQ,CACTjB,CAAM,CAANA,CAAM,CAANA,CAAAA,CACD,CARgC,CAAnCpB,CAWA,CAAA,CAAA,CAIF,MADA+B,CAAAA,CAAK,CAALA,CAAK,CAALA,CAAAA,CACA,CAAA,CA7DW,CAAA,CAgEbM,GAAG,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAqC,CAMtC,MALIH,CAAAA,CAAI,GAAR,CAAA,CAKA,CAJEH,CAAK,CAALA,CAAK,CAALA,CAAAA,CAIF,CAFEX,CAAM,CAANA,CAAM,CAANA,CAAAA,CAEF,GAtEW,CAAA,CAyEbkB,cAAc,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAA0B,CACtC,MAAOC,CAAAA,OAAO,CAAPA,cAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAP,CAAOA,CA1EI,CAAA,CA6EbC,cAAc,CAAA,CAAA,CAAA,CAAA,CAAoB,CAChC,MAAOD,CAAAA,OAAO,CAAPA,cAAAA,CAAAA,CAAAA,CAAP,CAAOA,CACR,CA/EY,CAF4C,CA8FvDE,CAAW,CAAGzC,MAAM,CAANA,MAAAA,CAAlB,CAAkBA,CA9FyC,CA+F3D,MAAO,IAAA,CAAA,KAAA,CAAA,CAAA,CAAP,CAAO,CA/FT,CA31BgC,CA68B1B0C,CAAS,CAAGC,CAAU,GAAK,CAC/BC,WAAW,CAAA,CAAA,CAAA,CAAA,CAAmB,GAAnB,CAAA,CAA4B,CACrCxB,CAAM,CAANA,WAAAA,CAAmBuB,CAAU,CAAVA,GAAAA,CAAnBvB,CAAmBuB,CAAnBvB,CAA6C,GAA7CA,CAAAA,CAF6B,CAAA,CAK/ByB,WAAW,CAAA,CAAA,CAAA,CAAA,CAAmB,CAC5B,MAAOzB,CAAAA,CAAM,CAANA,WAAAA,CAAmBuB,CAAU,CAAVA,GAAAA,CAA1B,CAA0BA,CAAnBvB,CANsB,CAAA,CAS/B0B,cAAc,CAAA,CAAA,CAAA,CAAA,CAAmB,CAC/B1B,CAAM,CAANA,cAAAA,CAAsBuB,CAAU,CAAVA,GAAAA,CAAtBvB,CAAsBuB,CAAtBvB,CACD,CAX8B,CAAL,CA78BI,CA49BhC,GAAI2B,CAAAA,CAAJ,GAAA,CA59BgC,KA89B1BC,CAAAA,CAAiB,CAAG,GAAA,CAAA,CAAA,CAAmBC,CAAQ,EACnD,UAAI,QAAA,CAAA,CAD+C,CAsB5C,SAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAkD,IAGvD,CAAA,CAHuD,CAevD,CAfuD,CACnDC,CAAJ,GADuD,CAInDC,CAAmB,CAAG,GAAA,CAAA,OAAA,CAAY9B,CAAO,EAAI,CAC/C+B,CAAmB,CAAG,SAAA,CAAA,CAAmB,CACvC,CADuC,GAErC7B,OAAO,CAAPA,IAAAA,CApgC6E,wPAogC7EA,CAAgD,GAAA,CAAA,KAAA,GAAhDA,KAAAA,CAFqC,CAGrCwB,CAAAA,GAHqC,EAKvCG,CAAAA,GALuC,CAMvC7B,CAAO,CAAPA,CAAO,CANT+B,CADF,CAA0B,CAJ6B,CAgBvD,GAAI,CACFC,CAAM,CAAGJ,CAAQ,CAAA,CAAA,CAAA,CAAA,CAAjBI,CAAiB,CADnB,CAEE,MAAA,CAAA,CAAY,CACZA,CAAM,CAAGC,OAAO,CAAPA,MAAAA,CAATD,CAASC,CACV,CAED,KAAMC,CAAAA,CAAgB,CAAGF,KAAAA,CAAAA,EAAmB/C,CAAU,CAtBC,CAsBD,CAAtD,CAKA,GAAI+C,KAAAA,CAAAA,EAAmB,CAAnBA,CAAAA,EAAwC,CAA5C,CAAA,CACE,SAOF,KAAMG,CAAAA,CAAkB,CAAI/C,CAAD,EAAa,CACtCA,CAAO,CAAPA,IAAAA,CAAagD,CAAG,EAAI,CAElBC,CAAY,CAAZA,CAAY,CAFdjD,CAAAA,CAGGkD,CAAK,EAAI,CAGV,GAAA,CAAA,CAAA,CAGEC,CANQ,CAIND,CAAK,GAAKA,CAAK,WAALA,CAAAA,KAAAA,EAAd,QACI,QAAOA,CAAAA,CAAK,CAAZ,OADK,CAJC,CAMEA,CAAK,CAAfC,OANQ,CAQRA,8BARQ,CAWVF,CAAY,CAAC,CACXG,iCADW,GAAA,CAEXD,OAAAA,CAAAA,CAFW,CAAD,CAddnD,CAAAA,EAAAA,KAAAA,CAkBSqD,CAAG,EAAI,CAEdvC,OAAO,CAAPA,KAAAA,CAAAA,yCAAAA,CAAAA,CAAAA,CApBFd,CAAAA,CApCqD,CAmCvD,CAmCA,MAPA,CAAA,CAOA,CANE+C,CAAkB,CAAlBA,CAAkB,CAMpB,CAJEA,CAAkB,CAAlBA,CAAkB,CAIpB,GAtEF,CAtBmD,CAEjD,CAFsB,CA99BM,CA8jC1BO,CAA0B,CAAG,CAAC,CAACzC,MAAD,CAACA,CAAD,CAASD,OAAAA,CAAAA,CAAT,CAAD,CAAA,CAAA,GAA8B,CAC3DpB,CAAa,CAAbA,OAAAA,CAAJ,SAD+D,CAKzDA,CAAa,CAAbA,OAAAA,CAAAA,SAAAA,CAAJ,OAAIA,GA3kCV,yDAskCmE,CAM3DoB,CAAO,EANoD,CAQ3DC,CAAM,CAACrB,CAAa,CAAbA,OAAAA,CAAPqB,SAAM,CARqD,CAUpD0C,CAAK,EAAIA,CAAK,CAAlB,iCAVwD,CAa7D1C,CAAM,CAAC,GAAA,CAAA,KAAA,CAAU0C,CAAK,CAAtB1C,OAAO,CAAD,CAbuD,CAe7DD,CAAO,CAAPA,CAAO,CAfX,CA9jCgC,CAilC1B4C,CAAkB,CAAG,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAkC,GAAlC,CAAA,GAA8C,CACvE,GAAIlD,CAAI,CAAJA,MAAAA,CAAcL,CAAQ,CAA1B,OAAA,CACE,KAAM,IAAA,CAAA,KAAA,CAAW,qBAAoBA,CAAQ,CAACM,OAAQ,IAAGJ,CAAkB,CAACF,CAAQ,CAAT,OAAA,CAAmB,QAAOO,CAAK,WAAUF,CAAI,CAACG,MAAzH,EAAM,CAAN,CAGF,GAAIH,CAAI,CAAJA,MAAAA,CAAcL,CAAQ,CAA1B,OAAA,CACE,KAAM,IAAA,CAAA,KAAA,CAAW,oBAAmBA,CAAQ,CAACS,OAAQ,IAAGP,CAAkB,CAACF,CAAQ,CAAT,OAAA,CAAmB,QAAOO,CAAK,WAAUF,CAAI,CAACG,MAAxH,EAAM,CAAN,CAGF,MAAO,IAAA,CAAA,OAAA,CAAY,CAAA,CAAA,CAAA,CAAA,GAAqB,CACtC,KAAMgD,CAAAA,CAAS,CAAG,CAA0B,CAA1B,IAAA,CAAA,IAAA,CAAsC,CAAC7C,OAAD,CAACA,CAAD,CAAUC,MAAAA,CAAAA,CAAV,CAAtC,CAAlB,CACAP,CAAI,CAAJA,IAAAA,CAAAA,CAAAA,CAFsC,CAGtCoD,CAAe,CAAfA,WAAAA,CAA4B,GAA5BA,CAAAA,CAHF,CAAO,CATT,CAjlCgC,CAimC1BC,CAAc,CAAG,CACrBC,OAAO,CAAE,CACPC,SAAS,CAAE5B,CAAS,CADb,CACa,CADb,CAEP6B,iBAAiB,CAAE7B,CAAS,CAFrB,CAEqB,CAFrB,CAGP8B,WAAW,CAAE,CAAkB,CAAlB,IAAA,CAAA,IAAA,CAAA,aAAA,CAA6C,CAACxD,OAAO,CAAR,CAAA,CAAaG,OAAO,CAAE,CAAtB,CAA7C,CAHN,CADY,CAMrBsD,IAAI,CAAE,CACJD,WAAW,CAAE,CAAkB,CAAlB,IAAA,CAAA,IAAA,CAAA,aAAA,CAA6C,CAACxD,OAAO,CAAR,CAAA,CAAaG,OAAO,CAAE,CAAtB,CAA7C,CADT,CANe,CAjmCS,CA2mC1BuD,CAAe,CAAG,CACtBC,KAAK,CAAE,CAAC3D,OAAO,CAAR,CAAA,CAAaG,OAAO,CAAE,CAAtB,CADe,CAEtBd,GAAG,CAAE,CAACW,OAAO,CAAR,CAAA,CAAaG,OAAO,CAAE,CAAtB,CAFiB,CAGtBkB,GAAG,CAAE,CAACrB,OAAO,CAAR,CAAA,CAAaG,OAAO,CAAE,CAAtB,CAHiB,CA3mCQ,CAsnChC,MANAjB,CAAAA,CAAW,CAAXA,OAAAA,CAAsB,CACpB0E,OAAO,CAAE,CAAC,IAAKF,CAAN,CADW,CAEpBG,QAAQ,CAAE,CAAC,IAAKH,CAAN,CAFU,CAGpBI,QAAQ,CAAE,CAAC,IAAKJ,CAAN,CAHU,CAMtB,CAAO7C,CAAU,CAAA,CAAA,CAAA,CAAA,CAAjB,CAAiB,CAtnCnB,CA+nCiBoD,EAAjBD,MAAiBC,CAxoCnB,CAAA,IA0oCED,CAAAA,CAAM,CAANA,OAAAA,CAAAA,O","sourcesContent":["/* webextension-polyfill - v0.6.0 - Mon Dec 23 2019 12:32:53 */\n/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */\n/* vim: set sts=2 sw=2 et tw=80: */\n/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\"use strict\";\n\nif (typeof browser === \"undefined\" || Object.getPrototypeOf(browser) !== Object.prototype) {\n const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = \"The message port closed before a response was received.\";\n const SEND_RESPONSE_DEPRECATION_WARNING = \"Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)\";\n\n // Wrapping the bulk of this polyfill in a one-time-use function is a minor\n // optimization for Firefox. Since Spidermonkey does not fully parse the\n // contents of a function until the first time it's called, and since it will\n // never actually need to be called, this allows the polyfill to be included\n // in Firefox nearly for free.\n const wrapAPIs = extensionAPIs => {\n // NOTE: apiMetadata is associated to the content of the api-metadata.json file\n // at build time by replacing the following \"include\" with the content of the\n // JSON file.\n const apiMetadata = {\n \"alarms\": {\n \"clear\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"clearAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"bookmarks\": {\n \"create\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getChildren\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getRecent\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getSubTree\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getTree\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"move\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeTree\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"search\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"update\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n }\n },\n \"browserAction\": {\n \"disable\": {\n \"minArgs\": 0,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"enable\": {\n \"minArgs\": 0,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"getBadgeBackgroundColor\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getBadgeText\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getPopup\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getTitle\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"openPopup\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"setBadgeBackgroundColor\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setBadgeText\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setIcon\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"setPopup\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setTitle\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n }\n },\n \"browsingData\": {\n \"remove\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"removeCache\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeCookies\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeDownloads\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeFormData\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeHistory\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeLocalStorage\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removePasswords\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removePluginData\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"settings\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"commands\": {\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"contextMenus\": {\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"update\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n }\n },\n \"cookies\": {\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAll\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAllCookieStores\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"set\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"devtools\": {\n \"inspectedWindow\": {\n \"eval\": {\n \"minArgs\": 1,\n \"maxArgs\": 2,\n \"singleCallbackArg\": false\n }\n },\n \"panels\": {\n \"create\": {\n \"minArgs\": 3,\n \"maxArgs\": 3,\n \"singleCallbackArg\": true\n }\n }\n },\n \"downloads\": {\n \"cancel\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"download\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"erase\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getFileIcon\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"open\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"pause\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeFile\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"resume\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"search\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"show\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n }\n },\n \"extension\": {\n \"isAllowedFileSchemeAccess\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"isAllowedIncognitoAccess\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"history\": {\n \"addUrl\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"deleteAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"deleteRange\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"deleteUrl\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getVisits\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"search\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"i18n\": {\n \"detectLanguage\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAcceptLanguages\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"identity\": {\n \"launchWebAuthFlow\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"idle\": {\n \"queryState\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"management\": {\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getSelf\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"setEnabled\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"uninstallSelf\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n }\n },\n \"notifications\": {\n \"clear\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"create\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getPermissionLevel\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"update\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n }\n },\n \"pageAction\": {\n \"getPopup\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getTitle\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"hide\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setIcon\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"setPopup\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setTitle\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"show\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n }\n },\n \"permissions\": {\n \"contains\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"request\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"runtime\": {\n \"getBackgroundPage\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getPlatformInfo\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"openOptionsPage\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"requestUpdateCheck\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"sendMessage\": {\n \"minArgs\": 1,\n \"maxArgs\": 3\n },\n \"sendNativeMessage\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"setUninstallURL\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"sessions\": {\n \"getDevices\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getRecentlyClosed\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"restore\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n }\n },\n \"storage\": {\n \"local\": {\n \"clear\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getBytesInUse\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"set\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"managed\": {\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getBytesInUse\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n }\n },\n \"sync\": {\n \"clear\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getBytesInUse\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"set\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n }\n },\n \"tabs\": {\n \"captureVisibleTab\": {\n \"minArgs\": 0,\n \"maxArgs\": 2\n },\n \"create\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"detectLanguage\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"discard\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"duplicate\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"executeScript\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getCurrent\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getZoom\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getZoomSettings\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"highlight\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"insertCSS\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"move\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"query\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"reload\": {\n \"minArgs\": 0,\n \"maxArgs\": 2\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeCSS\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"sendMessage\": {\n \"minArgs\": 2,\n \"maxArgs\": 3\n },\n \"setZoom\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"setZoomSettings\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"update\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n }\n },\n \"topSites\": {\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"webNavigation\": {\n \"getAllFrames\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getFrame\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"webRequest\": {\n \"handlerBehaviorChanged\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"windows\": {\n \"create\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getCurrent\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getLastFocused\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"update\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n }\n }\n };\n\n if (Object.keys(apiMetadata).length === 0) {\n throw new Error(\"api-metadata.json has not been included in browser-polyfill\");\n }\n\n /**\n * A WeakMap subclass which creates and stores a value for any key which does\n * not exist when accessed, but behaves exactly as an ordinary WeakMap\n * otherwise.\n *\n * @param {function} createItem\n * A function which will be called in order to create the value for any\n * key which does not exist, the first time it is accessed. The\n * function receives, as its only argument, the key being created.\n */\n class DefaultWeakMap extends WeakMap {\n constructor(createItem, items = undefined) {\n super(items);\n this.createItem = createItem;\n }\n\n get(key) {\n if (!this.has(key)) {\n this.set(key, this.createItem(key));\n }\n\n return super.get(key);\n }\n }\n\n /**\n * Returns true if the given object is an object with a `then` method, and can\n * therefore be assumed to behave as a Promise.\n *\n * @param {*} value The value to test.\n * @returns {boolean} True if the value is thenable.\n */\n const isThenable = value => {\n return value && typeof value === \"object\" && typeof value.then === \"function\";\n };\n\n /**\n * Creates and returns a function which, when called, will resolve or reject\n * the given promise based on how it is called:\n *\n * - If, when called, `chrome.runtime.lastError` contains a non-null object,\n * the promise is rejected with that value.\n * - If the function is called with exactly one argument, the promise is\n * resolved to that value.\n * - Otherwise, the promise is resolved to an array containing all of the\n * function's arguments.\n *\n * @param {object} promise\n * An object containing the resolution and rejection functions of a\n * promise.\n * @param {function} promise.resolve\n * The promise's resolution function.\n * @param {function} promise.rejection\n * The promise's rejection function.\n * @param {object} metadata\n * Metadata about the wrapped method which has created the callback.\n * @param {integer} metadata.maxResolvedArgs\n * The maximum number of arguments which may be passed to the\n * callback created by the wrapped async function.\n *\n * @returns {function}\n * The generated callback function.\n */\n const makeCallback = (promise, metadata) => {\n return (...callbackArgs) => {\n if (extensionAPIs.runtime.lastError) {\n promise.reject(extensionAPIs.runtime.lastError);\n } else if (metadata.singleCallbackArg ||\n (callbackArgs.length <= 1 && metadata.singleCallbackArg !== false)) {\n promise.resolve(callbackArgs[0]);\n } else {\n promise.resolve(callbackArgs);\n }\n };\n };\n\n const pluralizeArguments = (numArgs) => numArgs == 1 ? \"argument\" : \"arguments\";\n\n /**\n * Creates a wrapper function for a method with the given name and metadata.\n *\n * @param {string} name\n * The name of the method which is being wrapped.\n * @param {object} metadata\n * Metadata about the method being wrapped.\n * @param {integer} metadata.minArgs\n * The minimum number of arguments which must be passed to the\n * function. If called with fewer than this number of arguments, the\n * wrapper will raise an exception.\n * @param {integer} metadata.maxArgs\n * The maximum number of arguments which may be passed to the\n * function. If called with more than this number of arguments, the\n * wrapper will raise an exception.\n * @param {integer} metadata.maxResolvedArgs\n * The maximum number of arguments which may be passed to the\n * callback created by the wrapped async function.\n *\n * @returns {function(object, ...*)}\n * The generated wrapper function.\n */\n const wrapAsyncFunction = (name, metadata) => {\n return function asyncFunctionWrapper(target, ...args) {\n if (args.length < metadata.minArgs) {\n throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);\n }\n\n if (args.length > metadata.maxArgs) {\n throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);\n }\n\n return new Promise((resolve, reject) => {\n if (metadata.fallbackToNoCallback) {\n // This API method has currently no callback on Chrome, but it return a promise on Firefox,\n // and so the polyfill will try to call it with a callback first, and it will fallback\n // to not passing the callback if the first call fails.\n try {\n target[name](...args, makeCallback({resolve, reject}, metadata));\n } catch (cbError) {\n console.warn(`${name} API method doesn't seem to support the callback parameter, ` +\n \"falling back to call it without a callback: \", cbError);\n\n target[name](...args);\n\n // Update the API method metadata, so that the next API calls will not try to\n // use the unsupported callback anymore.\n metadata.fallbackToNoCallback = false;\n metadata.noCallback = true;\n\n resolve();\n }\n } else if (metadata.noCallback) {\n target[name](...args);\n resolve();\n } else {\n target[name](...args, makeCallback({resolve, reject}, metadata));\n }\n });\n };\n };\n\n /**\n * Wraps an existing method of the target object, so that calls to it are\n * intercepted by the given wrapper function. The wrapper function receives,\n * as its first argument, the original `target` object, followed by each of\n * the arguments passed to the original method.\n *\n * @param {object} target\n * The original target object that the wrapped method belongs to.\n * @param {function} method\n * The method being wrapped. This is used as the target of the Proxy\n * object which is created to wrap the method.\n * @param {function} wrapper\n * The wrapper function which is called in place of a direct invocation\n * of the wrapped method.\n *\n * @returns {Proxy<function>}\n * A Proxy object for the given method, which invokes the given wrapper\n * method in its place.\n */\n const wrapMethod = (target, method, wrapper) => {\n return new Proxy(method, {\n apply(targetMethod, thisObj, args) {\n return wrapper.call(thisObj, target, ...args);\n },\n });\n };\n\n let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);\n\n /**\n * Wraps an object in a Proxy which intercepts and wraps certain methods\n * based on the given `wrappers` and `metadata` objects.\n *\n * @param {object} target\n * The target object to wrap.\n *\n * @param {object} [wrappers = {}]\n * An object tree containing wrapper functions for special cases. Any\n * function present in this object tree is called in place of the\n * method in the same location in the `target` object tree. These\n * wrapper methods are invoked as described in {@see wrapMethod}.\n *\n * @param {object} [metadata = {}]\n * An object tree containing metadata used to automatically generate\n * Promise-based wrapper functions for asynchronous. Any function in\n * the `target` object tree which has a corresponding metadata object\n * in the same location in the `metadata` tree is replaced with an\n * automatically-generated wrapper function, as described in\n * {@see wrapAsyncFunction}\n *\n * @returns {Proxy<object>}\n */\n const wrapObject = (target, wrappers = {}, metadata = {}) => {\n let cache = Object.create(null);\n let handlers = {\n has(proxyTarget, prop) {\n return prop in target || prop in cache;\n },\n\n get(proxyTarget, prop, receiver) {\n if (prop in cache) {\n return cache[prop];\n }\n\n if (!(prop in target)) {\n return undefined;\n }\n\n let value = target[prop];\n\n if (typeof value === \"function\") {\n // This is a method on the underlying object. Check if we need to do\n // any wrapping.\n\n if (typeof wrappers[prop] === \"function\") {\n // We have a special-case wrapper for this method.\n value = wrapMethod(target, target[prop], wrappers[prop]);\n } else if (hasOwnProperty(metadata, prop)) {\n // This is an async method that we have metadata for. Create a\n // Promise wrapper for it.\n let wrapper = wrapAsyncFunction(prop, metadata[prop]);\n value = wrapMethod(target, target[prop], wrapper);\n } else {\n // This is a method that we don't know or care about. Return the\n // original method, bound to the underlying object.\n value = value.bind(target);\n }\n } else if (typeof value === \"object\" && value !== null &&\n (hasOwnProperty(wrappers, prop) ||\n hasOwnProperty(metadata, prop))) {\n // This is an object that we need to do some wrapping for the children\n // of. Create a sub-object wrapper for it with the appropriate child\n // metadata.\n value = wrapObject(value, wrappers[prop], metadata[prop]);\n } else if (hasOwnProperty(metadata, \"*\")) {\n // Wrap all properties in * namespace.\n value = wrapObject(value, wrappers[prop], metadata[\"*\"]);\n } else {\n // We don't need to do any wrapping for this property,\n // so just forward all access to the underlying object.\n Object.defineProperty(cache, prop, {\n configurable: true,\n enumerable: true,\n get() {\n return target[prop];\n },\n set(value) {\n target[prop] = value;\n },\n });\n\n return value;\n }\n\n cache[prop] = value;\n return value;\n },\n\n set(proxyTarget, prop, value, receiver) {\n if (prop in cache) {\n cache[prop] = value;\n } else {\n target[prop] = value;\n }\n return true;\n },\n\n defineProperty(proxyTarget, prop, desc) {\n return Reflect.defineProperty(cache, prop, desc);\n },\n\n deleteProperty(proxyTarget, prop) {\n return Reflect.deleteProperty(cache, prop);\n },\n };\n\n // Per contract of the Proxy API, the \"get\" proxy handler must return the\n // original value of the target if that value is declared read-only and\n // non-configurable. For this reason, we create an object with the\n // prototype set to `target` instead of using `target` directly.\n // Otherwise we cannot return a custom object for APIs that\n // are declared read-only and non-configurable, such as `chrome.devtools`.\n //\n // The proxy handlers themselves will still use the original `target`\n // instead of the `proxyTarget`, so that the methods and properties are\n // dereferenced via the original targets.\n let proxyTarget = Object.create(target);\n return new Proxy(proxyTarget, handlers);\n };\n\n /**\n * Creates a set of wrapper functions for an event object, which handles\n * wrapping of listener functions that those messages are passed.\n *\n * A single wrapper is created for each listener function, and stored in a\n * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener`\n * retrieve the original wrapper, so that attempts to remove a\n * previously-added listener work as expected.\n *\n * @param {DefaultWeakMap<function, function>} wrapperMap\n * A DefaultWeakMap object which will create the appropriate wrapper\n * for a given listener function when one does not exist, and retrieve\n * an existing one when it does.\n *\n * @returns {object}\n */\n const wrapEvent = wrapperMap => ({\n addListener(target, listener, ...args) {\n target.addListener(wrapperMap.get(listener), ...args);\n },\n\n hasListener(target, listener) {\n return target.hasListener(wrapperMap.get(listener));\n },\n\n removeListener(target, listener) {\n target.removeListener(wrapperMap.get(listener));\n },\n });\n\n // Keep track if the deprecation warning has been logged at least once.\n let loggedSendResponseDeprecationWarning = false;\n\n const onMessageWrappers = new DefaultWeakMap(listener => {\n if (typeof listener !== \"function\") {\n return listener;\n }\n\n /**\n * Wraps a message listener function so that it may send responses based on\n * its return value, rather than by returning a sentinel value and calling a\n * callback. If the listener function returns a Promise, the response is\n * sent when the promise either resolves or rejects.\n *\n * @param {*} message\n * The message sent by the other end of the channel.\n * @param {object} sender\n * Details about the sender of the message.\n * @param {function(*)} sendResponse\n * A callback which, when called with an arbitrary argument, sends\n * that value as a response.\n * @returns {boolean}\n * True if the wrapped listener returned a Promise, which will later\n * yield a response. False otherwise.\n */\n return function onMessage(message, sender, sendResponse) {\n let didCallSendResponse = false;\n\n let wrappedSendResponse;\n let sendResponsePromise = new Promise(resolve => {\n wrappedSendResponse = function(response) {\n if (!loggedSendResponseDeprecationWarning) {\n console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack);\n loggedSendResponseDeprecationWarning = true;\n }\n didCallSendResponse = true;\n resolve(response);\n };\n });\n\n let result;\n try {\n result = listener(message, sender, wrappedSendResponse);\n } catch (err) {\n result = Promise.reject(err);\n }\n\n const isResultThenable = result !== true && isThenable(result);\n\n // If the listener didn't returned true or a Promise, or called\n // wrappedSendResponse synchronously, we can exit earlier\n // because there will be no response sent from this listener.\n if (result !== true && !isResultThenable && !didCallSendResponse) {\n return false;\n }\n\n // A small helper to send the message if the promise resolves\n // and an error if the promise rejects (a wrapped sendMessage has\n // to translate the message into a resolved promise or a rejected\n // promise).\n const sendPromisedResult = (promise) => {\n promise.then(msg => {\n // send the message value.\n sendResponse(msg);\n }, error => {\n // Send a JSON representation of the error if the rejected value\n // is an instance of error, or the object itself otherwise.\n let message;\n if (error && (error instanceof Error ||\n typeof error.message === \"string\")) {\n message = error.message;\n } else {\n message = \"An unexpected error occurred\";\n }\n\n sendResponse({\n __mozWebExtensionPolyfillReject__: true,\n message,\n });\n }).catch(err => {\n // Print an error on the console if unable to send the response.\n console.error(\"Failed to send onMessage rejected reply\", err);\n });\n };\n\n // If the listener returned a Promise, send the resolved value as a\n // result, otherwise wait the promise related to the wrappedSendResponse\n // callback to resolve and send it as a response.\n if (isResultThenable) {\n sendPromisedResult(result);\n } else {\n sendPromisedResult(sendResponsePromise);\n }\n\n // Let Chrome know that the listener is replying.\n return true;\n };\n });\n\n const wrappedSendMessageCallback = ({reject, resolve}, reply) => {\n if (extensionAPIs.runtime.lastError) {\n // Detect when none of the listeners replied to the sendMessage call and resolve\n // the promise to undefined as in Firefox.\n // See https://github.com/mozilla/webextension-polyfill/issues/130\n if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {\n resolve();\n } else {\n reject(extensionAPIs.runtime.lastError);\n }\n } else if (reply && reply.__mozWebExtensionPolyfillReject__) {\n // Convert back the JSON representation of the error into\n // an Error instance.\n reject(new Error(reply.message));\n } else {\n resolve(reply);\n }\n };\n\n const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {\n if (args.length < metadata.minArgs) {\n throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);\n }\n\n if (args.length > metadata.maxArgs) {\n throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);\n }\n\n return new Promise((resolve, reject) => {\n const wrappedCb = wrappedSendMessageCallback.bind(null, {resolve, reject});\n args.push(wrappedCb);\n apiNamespaceObj.sendMessage(...args);\n });\n };\n\n const staticWrappers = {\n runtime: {\n onMessage: wrapEvent(onMessageWrappers),\n onMessageExternal: wrapEvent(onMessageWrappers),\n sendMessage: wrappedSendMessage.bind(null, \"sendMessage\", {minArgs: 1, maxArgs: 3}),\n },\n tabs: {\n sendMessage: wrappedSendMessage.bind(null, \"sendMessage\", {minArgs: 2, maxArgs: 3}),\n },\n };\n const settingMetadata = {\n clear: {minArgs: 1, maxArgs: 1},\n get: {minArgs: 1, maxArgs: 1},\n set: {minArgs: 1, maxArgs: 1},\n };\n apiMetadata.privacy = {\n network: {\"*\": settingMetadata},\n services: {\"*\": settingMetadata},\n websites: {\"*\": settingMetadata},\n };\n\n return wrapObject(extensionAPIs, staticWrappers, apiMetadata);\n };\n\n if (typeof chrome != \"object\" || !chrome || !chrome.runtime || !chrome.runtime.id) {\n throw new Error(\"This script should only be loaded in a browser extension.\");\n }\n\n // The build process adds a UMD wrapper around this file, which makes the\n // `module` variable available.\n module.exports = wrapAPIs(chrome);\n} else {\n module.exports = browser;\n}\n"],"file":"browser-polyfill.min.js"} ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������browser-polyfill.min.js.map.license�����������������������������������������������������������������0000664�0000000�0000000�00000000115�14607666711�0033740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/js������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: Mozilla Foundation SPDX-License-Identifier: MPL-2.0 ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/js/popup.js��������������������������0000664�0000000�0000000�00000011677�14607666711�0026707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later 'use strict'; var CONNECTED = false; var DEVICES = []; // eslint-disable-next-line no-unused-vars var TARGET_URL = null; // Suppress errors caused by Mozilla polyfill // TODO: not sure if these are relevant anymore const _MUTE = [ 'Could not establish connection. Receiving end does not exist.', 'The message port closed before a response was received.', ]; // Simple error logging function // eslint-disable-next-line no-redeclare function logError(error) { if (!_MUTE.includes(error.message)) console.error(error.message); } /** * Share a URL, either direct to the browser or by SMS * * @param {string} device - The deviceId * @param {string} action - Currently either 'share' or 'telephony' * @param {string} url - The URL to share */ async function sendUrl(device, action, url) { try { window.close(); await browser.runtime.sendMessage({ type: 'share', data: { device: device, url: url, action: action, }, }); } catch (e) { logError(e); } } /** * Create and return a device element for the popup menu * * @param {object} device - A JSON object describing a connected device * @return {HTMLElement} - A <div> element with icon, name and actions */ function getDeviceElement(device) { const deviceElement = document.createElement('div'); deviceElement.className = 'device'; const deviceIcon = document.createElement('img'); deviceIcon.className = 'device-icon'; deviceIcon.src = `images/${device.type}.svg`; deviceElement.appendChild(deviceIcon); const deviceName = document.createElement('span'); deviceName.className = 'device-name'; deviceName.textContent = device.name; deviceElement.appendChild(deviceName); if (device.share) { const shareButton = document.createElement('img'); shareButton.className = 'plugin-button'; shareButton.src = 'images/open-in-browser.svg'; shareButton.title = browser.i18n.getMessage('shareMessage'); shareButton.addEventListener( 'click', () => sendUrl(device.id, 'share', URL) ); deviceElement.appendChild(shareButton); } if (device.telephony) { const telephonyButton = document.createElement('img'); telephonyButton.className = 'plugin-button'; telephonyButton.src = 'images/message.svg'; telephonyButton.title = browser.i18n.getMessage('smsMessage'); telephonyButton.addEventListener( 'click', () => sendUrl(device.id, 'telephony', URL) ); deviceElement.appendChild(telephonyButton); } return deviceElement; } /** * Populate the browserAction popup */ function setPopup() { const devNode = document.getElementById('popup'); while (devNode.hasChildNodes()) devNode.removeChild(devNode.lastChild); if (CONNECTED && DEVICES.length) { for (const device of DEVICES) { const deviceElement = getDeviceElement(device); devNode.appendChild(deviceElement); } return; } // Disconnected or no devices const message = document.createElement('span'); message.className = 'popup-menu-message'; devNode.appendChild(message); // The native-messaging-host or service is disconnected if (!CONNECTED) message.textContent = browser.i18n.getMessage('popupMenuDisconnected'); // There are no devices else message.textContent = browser.i18n.getMessage('popupMenuNoDevices'); } /** * Callback for receiving a message forwarded by background.js * * @param {Object} message - A JSON message object * @param {runtime.MessageSender} sender - The sender of the message. */ function onPortMessage(message, sender) { try { // console.log(`WebExtension-popup RECV: ${JSON.stringify(message)}`); if (sender.url.includes('/background.html')) { if (message.type === 'connected') { CONNECTED = message.data; } else if (message.type === 'devices') { CONNECTED = true; DEVICES = message.data; } setPopup(); } } catch (e) { logError(e); } } /** * Set the current URL and repopulate the popup, on-demand */ async function onPopup() { try { const tabs = await browser.tabs.query({ active: true, currentWindow: true, }); if (tabs.length) TARGET_URL = tabs[0].url; setPopup(); await browser.runtime.sendMessage({type: 'devices'}); } catch (e) { logError(e); } } /** * Startup: listen for forwarded messages and populate the popup on-demand */ browser.runtime.onMessage.addListener(onPortMessage); document.addEventListener('DOMContentLoaded', onPopup); �����������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/manifest.chrome.json�����������������0000664�0000000�0000000�00000002166�14607666711�0030540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "manifest_version": 2, "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", "version": "8", "homepage_url": "https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki", "default_locale": "en", "browser_action": { "default_title": "__MSG_extensionName__", "default_popup": "popup.html", "default_icon": { "256": "images/gsconnect-256.png", "128": "images/gsconnect-128.png", "64": "images/gsconnect-64.png", "48": "images/gsconnect-48.png", "32": "images/gsconnect-32.png", "24": "images/gsconnect-24.png", "22": "images/gsconnect-22.png", "16": "images/gsconnect-16.png" } }, "background": { "page": "background.html" }, "permissions": [ "nativeMessaging", "tabs", "contextMenus" ], "icons": { "256": "images/gsconnect-256.png", "128": "images/gsconnect-128.png", "64": "images/gsconnect-64.png", "48": "images/gsconnect-48.png", "32": "images/gsconnect-32.png", "24": "images/gsconnect-24.png", "22": "images/gsconnect-22.png", "16": "images/gsconnect-16.png" } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/manifest.chrome.json.license���������0000664�0000000�0000000�00000000164�14607666711�0032155�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/manifest.firefox.json����������������0000664�0000000�0000000�00000001613�14607666711�0030721�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "manifest_version": 2, "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", "version": "8", "homepage_url": "https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki", "default_locale": "en", "browser_action": { "default_title": "__MSG_extensionName__", "default_popup": "popup.html", "default_icon": "images/gsconnect.svg" }, "applications": { "gecko": { "id": "gsconnect@andyholmes.github.io" } }, "background": { "page": "background.html" }, "permissions": [ "nativeMessaging", "tabs", "contextMenus" ], "icons": { "256": "images/gsconnect.svg", "128": "images/gsconnect.svg", "64": "images/gsconnect.svg", "48": "images/gsconnect.svg", "32": "images/gsconnect.svg", "24": "images/gsconnect.svg", "22": "images/gsconnect.svg", "16": "images/gsconnect.svg" } } ���������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/manifest.firefox.json.license��������0000664�0000000�0000000�00000000164�14607666711�0032342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/mkwebext.sh��������������������������0000775�0000000�0000000�00000004361�14607666711�0026747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later # A script for building GSConnect WebExtension zips for Chrome or Firefox. # TODO: Mozilla Firefox extension requires node 'web-ext' # Update translations if [ "${1}" == "i18n" ]; then echo -n "Updating translations..." ./gettext.js echo "done" exit # Common preparation for chrome & firefox elif [ "${1}" == "chrome" ] || [ "${1}" == "firefox" ]; then # Clean-up old files rm -rf ${1}.zip # Copy relevant files mkdir ${1} mkdir ${1}/images cp background.html ${1}/ cp manifest.${1}.json ${1}/manifest.json cp popup.html ${1}/ cp stylesheet.css ${1}/ mkdir ${1}/js cp js/*.js ${1}/js/ cp js/*.map ${1}/js/ # Copy translations for dir in ./_locales/*; do localedest=${1}/_locales/$(basename ${dir}) mkdir -p ${localedest} cp ${dir}/*.json ${localedest}/ done fi # Build Mozilla Firefox Add-on if [ "${1}" == "firefox" ]; then echo -n "Building Mozilla Firefox Add-on..." # Firefox only needs SVG cp -R images/*.svg ${1}/images # Make the ZIP ~/node_modules/.bin/web-ext -s ${1} build > /dev/null 2>&1 mv web-ext-artifacts/gsconnect-*.zip ${1}.zip # Cleanup rm -rf ${1} web-ext-artifacts echo "done" exit # Build Google Chrome/Chromium Extension elif [ "${1}" == "chrome" ]; then echo -n "Building Google Chrome/Chromium Extension..." # Remove Firefox-only features sed -i '/FIREFOX-ONLY/{N;d}' ${1}/js/background.js sed -i '/FIREFOX-ONLY/{N;d}' ${1}/js/popup.js # Chrome needs SVG and PNG cp -R images/* ${1}/images/ # Make the ZIP cd chrome/ zip -r ../chrome.zip * > /dev/null 2>&1 # Cleanup cd .. rm -rf ${1} echo "done" exit fi # Usage echo "Usage: mkwebext [firefox|chrome|i18n]" echo "Build an unsigned ZIP of the WebExtension for Chrome or Firefox." echo echo " chrome Build Google Chrome/Chromium Extension (unsigned zip)" echo " firefox Build Mozilla Firefox Add-on zip (unsigned zip)" echo " i18n Update translations" echo "" echo "Building the Mozilla Firefox extension requires the 'web-ext' node module." exit 1 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/popup.html���������������������������0000664�0000000�0000000�00000000670�14607666711�0026612�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!-- SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later --> <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="stylesheet.css" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <div id="popup"/> <script src="js/browser-polyfill.min.js"></script> <script src="js/popup.js"></script> </body> </html> ������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-43258f9/webextension/stylesheet.css�����������������������0000664�0000000�0000000�00000001566�14607666711�0027471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect * * SPDX-License-Identifier: GPL-2.0-or-later */ html, body { margin: 0; padding: 0; background-color: #fafafa; font-family: Ubuntu, Cantrell, Arial, sans-serif; font-size: 0.9em; } #popup { display: flex; flex-direction: column; justify-content: center; min-width: 15em; margin: 0.5em 0; } .device { white-space: nowrap; display: flex; align-items: center; } .device-icon { margin: 0.5em; } .device-name { margin-right: auto; } .plugin-button { border-radius: 3px; margin-right: 6px; padding: 4px; } .plugin-button:hover { background-color: rgba(204, 204, 204, 0.5); } .plugin-button:active { background-color: rgba(204, 204, 204, 0.75); } .popup-menu-message { font-style: italic; text-align: center; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������