pax_global_header00006660000000000000000000000064147717763740014541gustar00rootroot0000000000000052 comment=ea898217ac2e22fd6915e8ca69bb70f9fb738c34 GSConnect-gnome-shell-extension-gsconnect-ea89821/000077500000000000000000000000001477177637400221325ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/.editorconfig000066400000000000000000000014211477177637400246050ustar00rootroot00000000000000# 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-ea89821/.flake8000066400000000000000000000002571477177637400233110ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later [flake8] extend-ignore = E402 max-line-length = 80 GSConnect-gnome-shell-extension-gsconnect-ea89821/.github/000077500000000000000000000000001477177637400234725ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/.github/ISSUE_TEMPLATE/000077500000000000000000000000001477177637400256555ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/.github/ISSUE_TEMPLATE/bug_report.yml000066400000000000000000000060501477177637400305510ustar00rootroot00000000000000# 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-ea89821/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002261477177637400276450ustar00rootroot00000000000000# 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-ea89821/.github/ISSUE_TEMPLATE/feature_request.yml000066400000000000000000000052251477177637400316070ustar00rootroot00000000000000# 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-ea89821/.github/dependabot.yml000066400000000000000000000003521477177637400263220ustar00rootroot00000000000000# 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-ea89821/.github/labeler.yml000066400000000000000000000002571477177637400256270ustar00rootroot00000000000000# 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-ea89821/.github/workflows/000077500000000000000000000000001477177637400255275ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/.github/workflows/ci.yml000066400000000000000000000053361477177637400266540ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: CI on: push: branches: - main - 'backports/gnome-*' pull_request: branches: - main - 'backports/gnome-*' workflow_dispatch: jobs: select: runs-on: ubuntu-latest outputs: image_id: ${{ steps.select-image.outputs.image_id }} steps: - id: step1 if: ${{ ! startsWith(github.event_name, 'pull_request') }} run: echo "target=${{ github.ref_name }}" >> $GITHUB_OUTPUT - id: step2 if: ${{ startsWith(github.event_name, 'pull_request') }} run: echo "target=${{ github.base_ref }}" >> $GITHUB_OUTPUT - id: select-image env: TARGET_BRANCH: ${{ steps.step1.outputs.target || steps.step2.outputs.target }} run: | if [[ \ ${{ startsWith(env.TARGET_BRANCH, 'backports/') && 1 || 0 }} == 1 \ ]]; then IMAGE_ID=$(echo "$TARGET_BRANCH" | sed -e 's,^backports/,,') echo "image_id=$IMAGE_ID" >> $GITHUB_OUTPUT else echo "image_id=latest" >> $GITHUB_OUTPUT fi test: needs: select runs-on: ubuntu-latest container: image: ghcr.io/gsconnect/gsconnect-ci:${{ needs.select.outputs.image_id }} steps: - uses: actions/checkout@v4 - name: Install node modules run: npm install - 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 if [[ \ ${{ startsWith(env.TARGET_BRANCH, 'backports/gnome-42') && 1 || 0 }} == 1 \ ]]; then glib-compile-schemas /usr/share/glib-2.0/schemas/ fi 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-ea89821/.github/workflows/issue-labeler.yml000066400000000000000000000006341477177637400310110ustar00rootroot00000000000000# 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-ea89821/.github/workflows/label-conflicts.yml000066400000000000000000000022351477177637400313150ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: "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: label: runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: check if PRs are mergeable uses: eps1lon/actions-label-merge-conflict@v3.0.2 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-ea89821/.github/workflows/reuse.yml000066400000000000000000000006241477177637400273770ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later name: REUSE on: push: branches: - main pull_request: branches: - main workflow_dispatch: jobs: verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: REUSE Compliance Check uses: fsfe/reuse-action@v4.0.0 GSConnect-gnome-shell-extension-gsconnect-ea89821/.gitignore000066400000000000000000000002721477177637400241230ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later _build *~ webextension/*.zip /node_modules /package-lock.json GSConnect-gnome-shell-extension-gsconnect-ea89821/CODE_OF_CONDUCT.md000066400000000000000000000005271477177637400247350ustar00rootroot00000000000000 # 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-ea89821/CONTRIBUTING.md000066400000000000000000000142131477177637400243640ustar00rootroot00000000000000 # 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-ea89821/LICENSES/000077500000000000000000000000001477177637400233375ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/LICENSES/CC-BY-4.0.txt000066400000000000000000000411771477177637400252060ustar00rootroot00000000000000Creative Commons Attribution 4.0 International Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. Creative Commons Attribution 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 – Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 – Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: A. reproduce and Share the Licensed Material, in whole or in part; and B. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 5. Downstream recipients. A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 – License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: A. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. Section 4 – Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 – Disclaimer of Warranties and Limitation of Liability. a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 – Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 – Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 – Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. GSConnect-gnome-shell-extension-gsconnect-ea89821/LICENSES/CC-BY-SA-3.0.txt000066400000000000000000000533401477177637400255010ustar00rootroot00000000000000Creative Commons Legal Code Attribution-ShareAlike 3.0 Unported CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. License THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. c. "Creative Commons Compatible License" means a license that is listed at https://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, d. to Distribute and Publicly Perform Adaptations. e. For the avoidance of doubt: i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. Creative Commons Notice Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. Creative Commons may be contacted at https://creativecommons.org/. GSConnect-gnome-shell-extension-gsconnect-ea89821/LICENSES/CC0-1.0.txt000066400000000000000000000156101477177637400247440ustar00rootroot00000000000000Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. GSConnect-gnome-shell-extension-gsconnect-ea89821/LICENSES/GPL-2.0-or-later.txt000066400000000000000000000416711477177637400265530ustar00rootroot00000000000000GNU 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-ea89821/LICENSES/MPL-2.0.txt000066400000000000000000000405271477177637400250350ustar00rootroot00000000000000Mozilla 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-ea89821/README.md000066400000000000000000000070121477177637400234110ustar00rootroot00000000000000 # 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-ea89821/RELEASE_CHECKLIST.md000066400000000000000000000037321477177637400251320ustar00rootroot00000000000000 # Release Checklist Steps to prepare a new release. ## Submit a PR to update the version metadata - [ ] Bump the `project(version)` value in `meson.build` - [ ] Add a corresponding `...` block to the top of the `` section in `data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in` - [ ] Merge the PR to create a release commit > [!CAUTION] > The AppStream metadata will contain a release date for the new version. > If the PR isn't merged immediately, > double-check that it matches the current date before merging. ## Package the new release - [ ] Run `git pull` on the `main` branch - [ ] Run `meson setup _build .` - [ ] Run `meson compile -C _build make-zip` ## Publish to GitHub Releases - [ ] Open the [New Release template] (or click "Draft a new release" from the Releases page) - [ ] Create a new `v##` tag matching the new version number - [ ] Click "Generate release notes" to populate the textarea with an autogenerated list of PRs, as a starting point - [ ] Clean up & compose the final notes, which may mean... - [ ] Remove automated PRs (dependabot, Crowdin) - [ ] Remove purely-internal PRs, like CI, build, or repo-maintenance changes. Anything that affects the _repo_, but not the _package_, is probably not relevant to endusers and can be left out. - [ ] (Optional) Write an introductory "What's New" section, highlighting any notable changes or new features. - [ ] Attach the `_build/gsconnect@andyholmes.github.io.zip` file - [ ] Publish the release ## Submit to EGO Once the release is published to GitHub, the attached zip file can also be submitted to extensions.gnome.org by the designated extension author (currently @daniellandau). [New Release template]: https://github.com/GSConnect/gnome-shell-extensions-gsconnect/releases/new GSConnect-gnome-shell-extension-gsconnect-ea89821/REUSE.toml000066400000000000000000000031231477177637400237110ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later version = 1 SPDX-PackageName = "gnome-shell-extension-gsconnect" SPDX-PackageSupplier = "GSConnect Developers <>" SPDX-PackageDownloadLocation = "https://github.com/GSConnect/gnome-shell-extension-gsconnect" [[annotations]] path = ["data/icons/**", "data/images/enter-keyboard-shortcut.svg", "data/metainfo/**.png"] precedence = "aggregate" SPDX-FileCopyrightText = "GSConnect Developers <>" SPDX-License-Identifier = "GPL-2.0-or-later" # According to https://developer.chrome.com/docs/webstore/branding [[annotations]] path = "data/images/chrome-badge.png" precedence = "aggregate" SPDX-FileCopyrightText = "Google " SPDX-License-Identifier = "CC-BY-4.0" # According to # https://extensionworkshop.com/documentation/publish/promoting-your-extension/ [[annotations]] path = "data/images/firefox-badge.png" precedence = "aggregate" SPDX-FileCopyrightText = "1998–2024 by individual mozilla.org contributors <>" SPDX-License-Identifier = "CC-BY-SA-3.0" [[annotations]] path = ["po/**.po", "po/**.pot"] precedence = "aggregate" SPDX-FileCopyrightText = "GSConnect Community" SPDX-License-Identifier = "GPL-2.0-or-later" [[annotations]] path = "installed-tests/data/**" precedence = "aggregate" SPDX-FileCopyrightText = "GSConnect Developers <>" SPDX-License-Identifier = "GPL-2.0-or-later" [[annotations]] path = "webextension/images/**" precedence = "aggregate" SPDX-FileCopyrightText = "GSConnect Developers <>" SPDX-License-Identifier = "GPL-2.0-or-later" GSConnect-gnome-shell-extension-gsconnect-ea89821/build-aux/000077500000000000000000000000001477177637400240245ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/build-aux/ego/000077500000000000000000000000001477177637400245765ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/build-aux/ego/mkzip.sh000077500000000000000000000017001477177637400262650ustar00rootroot00000000000000#!/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} # 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 gnome-extensions install --force ${ZIP_FILE} fi GSConnect-gnome-shell-extension-gsconnect-ea89821/crowdin.yml000066400000000000000000000007601477177637400243250ustar00rootroot00000000000000# 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-ea89821/data/000077500000000000000000000000001477177637400230435ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/application.css000066400000000000000000000041761477177637400260700ustar00rootroot00000000000000/* * 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-ea89821/data/config.js.in000066400000000000000000000013161477177637400252540ustar00rootroot00000000000000// 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-ea89821/data/firewalld/000077500000000000000000000000001477177637400250145ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/firewalld/gsconnect.xml000066400000000000000000000006701477177637400275240ustar00rootroot00000000000000 GSConnect KDE Connect implementation for GNOME GSConnect-gnome-shell-extension-gsconnect-ea89821/data/firewalld/meson.build000066400000000000000000000004351477177637400271600ustar00rootroot00000000000000# 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-ea89821/data/icons/000077500000000000000000000000001477177637400241565ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/computer-symbolic.svg000066400000000000000000000051511477177637400303560ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/group-avatar-symbolic.svg000066400000000000000000000011121477177637400311210ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/laptop-symbolic.svg000066400000000000000000000015051477177637400300160ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/mouse-left-button-symbolic.svg000066400000000000000000000012511477177637400321060ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/mouse-right-button-symbolic.svg000066400000000000000000000012511477177637400322710ustar00rootroot00000000000000 mouse-with-smaller-scrollwheel-symbolic.svg000066400000000000000000000011621477177637400345160ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons org.gnome.Shell.Extensions.GSConnect-symbolic.svg000066400000000000000000000065011477177637400353620ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme org.gnome.Shell.Extensions.GSConnect.svg000066400000000000000000000433351477177637400335510ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/phonelink-delete-symbolic.svg000066400000000000000000000106231477177637400317470ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/phonelink-lock-symbolic.svg000066400000000000000000000111721477177637400314350ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/phonelink-off-symbolic.svg000066400000000000000000000071341477177637400312620ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/phonelink-ring-symbolic.svg000066400000000000000000000123651477177637400314510ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/phonelink-setup-symbolic.svg000066400000000000000000000127751477177637400316570ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/phonelink-symbolic.svg000066400000000000000000000065011477177637400305070ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/smartphone-symbolic.svg000066400000000000000000000077261477177637400307120ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/sms-send.svg000066400000000000000000000003001477177637400264210ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/sms-symbolic.svg000066400000000000000000000011721477177637400273210ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/tablet-symbolic.svg000066400000000000000000000007531477177637400277760ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/icons/tv-symbolic.svg000066400000000000000000000110421477177637400271450ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme GSConnect-gnome-shell-extension-gsconnect-ea89821/data/images/000077500000000000000000000000001477177637400243105ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/images/chrome-badge.png000066400000000000000000000072621477177637400273420ustar00rootroot00000000000000PNG  IHDR:ftEXtSoftwareAdobe ImageReadyqe<TIDATx]{pTWvdžwb Sy:H)0ՎSΈPkũZZLjX@U6:" "iR XK a7dͽwY`Se7w99w[$2 Nl8AF>|0~ x6C#q!@ 8G d _;::(U6diRMKQ6Dɶ@\5`$[Phj7m”R$u1'&N>fu"(0ΫW%ܹ1NIFGaQ-'MaΜ=u} zT;XnNmd2NAƍ*ĺuuuq|{GBF:ݷ 9zB %Cj#ERc{݉kl6@Us@N ̄h6'PFhÓ_*Ufu_@ =KU+WFqNqR&LX2%\V+PL#ǎ+rZU.OuU%}tVs\n44B $ڟ;g6=;9]spǩRcP#Ӧ& ;A ˜06Eh׃D߂<0)ή5JO67+၀r,O@P(, /hC()i L&C e@070QHauTީC?ʂz[ @*&AVP})i$S xPO" m DzKԁ0pA(AA?-HhzխJ2mۗ*i.[)UOw!/,lؚD?Lj$ͮZ~[JCXp`I 0`p|J!gζ8&M[Ț{Z1P]x@<K:Xܧ:oX> bq@epCEgR K2",gI % eAB25dr…K&Im< pVKfo$8q|8bm@Ar QV\ 9vv뜀r` zYhuN=Czq|d&Rz!sRD ۭ OWeQebq^)ɸ"L8Q 'hׅgq.PS4Nю+ ? *k?m:gϓ'&3]zƉ`m0 R Ui ߣTT|rUH7P X {wzXO(AD;ڿ7I41?P{q#8~?E`V^H8Q$FN(^FqmZ:mC٠׎CJtlɮ>)Tr~cxOP"LgҐƜE>ӄ,!2~H1in'q MMn\e1ZOn4͊4 RĬ~Xʆ^98cGƐ5 8o.oQ)h}V>_!ݷ m+!P>բQ\?C:Co^ֈDrIcoalܴq*k~>@ՓxaZ`5"kmT'44xB۳B9^̉{gj :|J+aF\QΉ3pp϶gNWҤB/דoSaUb':} r!U˼\28STBX0B}Y@諪ѧ>6^mVN0n/f?V[`7ƁepжukE8|0m :1vN, 8X3S=ݙ}B d*,C Mӣ[^otTua(ci}>AhZ(Au Ŵ2qL\c[bCAGۦ_ u B16(,;bY3LVLc0̈́98=+)iuK֡h|ϘE3GTM\q:vm**VBDc3{\ؽAL֝7cӛ㮎)(kT8Pqy]ǂ 4vZ]+l4 DxۄG Hފn(juoh[hނ$(h5ޏ@|ϴ+85QWef7hTս*TbVʴ:=JwSFd2hxl-OW)X.kݙtor*Z=*w[D<3Ax Z%t\15e%X+#.LTLljn4x|wnNO^=}D9#`Om {䫙LWӗ=t%nJM'h4P2LVgK0uY]V!Ŷ!q&OPиzΖIbaىqY3nֶJظ\(ls[,gKҽqSL21:SRH3i+c~)Ec+g 2mzK;zipVqMpx'&xH'~gkƌXpNp֒=o$'o,NSyWɡ%k]=OȲŰ4sϿ8祇MTސ":oQy85p6ceѴ,4$噎cXd  Kb ~Ez__\X]X^/+27V&HNsLcw?{EqnH$o|skMt0*BRaiaw3̉1uK䍼J R<ڔ{_#NJ ̹0Ij:4 /[<{ę/}EPs-z_<ωǡqnm+87vcֵv8Lo%jwY% =͓aOκ%Qeq r'%nݵE89qMi.-ѫSǨk`yN_HVh) 9B_/6c"3`)E)#.}r4ND %a7M'QW;1#K -5V˓qswSvNV35eq2q)1:]OF`oho?yR wy OGb-Rq)D35+S "*//'-)Ϊ w*rU$B@#q!@ PW˝2$Φkr' ۫>!k IENDB`GSConnect-gnome-shell-extension-gsconnect-ea89821/data/images/enter-keyboard-shortcut.svg000066400000000000000000000433331477177637400316230ustar00rootroot00000000000000 image/svg+xml GSConnect-gnome-shell-extension-gsconnect-ea89821/data/images/firefox-badge.png000066400000000000000000000140751477177637400275270ustar00rootroot00000000000000PNG  IHDR<WQIDATxwxTEۇR "R{-JB/)* H*"P Ҥ@(B(ifgwO A\׹)ss~3g:/z,Lbʦ2$b=c72p[X,x,|*hurXD OA+[aFӒ̀Dq43+Rw哎Z/J4pR4d7&,Yc ْX[̍+VdPBik"䣔K8Rhj|[#P籿*t%mRZ)-~` (Y`t'\ kFP!`] yEj JA))cVqt"] #7j 2Eb3{"uI v(`_M: .J߂ӧZRVhe;SŬ/ R륳wzC\T5Ы{ VAf5oɑ lHXsQgl}EzAtO8cSAVVoVcٞz%E"a*7 3+,S$meܮl[}3SuT#<ȃK:#&X} x–aTH:JY;x}wG{NកTU L?Nʱ]`D%S&JTIO?p{uDWfψs#fυuaW= ^F]+VdjspƏ+S=8cHe)iy$$H8{x,|QV`l3F؁=ƍ;[61itpg|nv]N#pF]{N{D޳>pഎ:|PKmK EZDF\= y ۳|VN$0oK< i$_[~Q6߅oà7axe*/ eQ/~{02Y~dlpOr^b%Oe?'%uTW8i^pN?pb wG6Ż?g9vhPJmZ\Z(դU*c@w |.о_WZQmYrdF,6eYi̮ NpCq?Hp,v@Ɵ遼5}_IE-s+/N h_;>}?S'&hRyPixVEUɚؔ-Lw~Y{5&x=2k=38TCF|>i{>=7H uX﵉VT7g+/Vۦ@ZFh?@eU+MӖvNeXX972Xp > we6at $s~,n z>oP  `uhיrI˹l-mKj%>ׅ _TĪ0 X|Ta[0U>\0@uX&wf2x8 C00%7Y,390.N7 ;a_aZ\;֘3w= ^~VU {A,a!_5I&Y~[*6>ZBL}c{GªYY V5aS-Rք`E5H[kǁqzyxss;ȥE퍻& lް/+D ^s׽*H #6v }kU`kow`] ly~;ï ࠼r!F,cjf4 ; ,,~p`Ck[CH]s~?l/NO A]: `~w[vaZA+[hcG5!Mg9lˍI#ؙ062?frG$5Ԇw|~YL 8wq+Yvs:XA!B7Ap4W~UnhڷPpgh@Dڮ0_ekB/KbT i~0,>#1cE0c&5y@B]&d.)$8z96&vcò@X kB R=- !&KO~"7|s):>l^vu_j]ҶzHyK}/n\(`'6{ie!-/ѵ߿h]C;0@+_9G^b@5>Š6w;Zgd4ǸFwr~q ): u` s`~(lÖWζ;CbŁ4ϺgiqƼ4tҞYjeն-ggZjj Fs`%%վ`{@wZGPmg6}H|KSg%=3WG1WuɠlDe3i6oMUf2cX=ҸT6ԇ.05fk8EΆP[rɷO:0v@IDT4\^yVϫܗ iRI[o%`>Ծa ay ?L6t@khkeit_G6~f4][g=c.}Wlxu.i@X+laL A]E:v;Cʆׅ?oڟ%PkEKUI>,JZlRRR946RbDQṣAնK=}/ A|M@Cd6-kwp8V뺞~[ivh(Z14#5Ϡ > 9`|P 57Y, <;HD?!9a#luё(F5?g9Yߩ'CuVQ -Z X{_i֛b QUQ^"mm5wk7/o#ahFNԴ3pktMkD{%;niyT[DpOS m2mo`f]a p\ 68&g/"gN#r5{\4$ C{|ZijGOKͪqvl)l+ta0#- 2L0,ʜ=h:ipimavơZ;A'{k maP(yE05Ѡ|?"EWqD8RNuZ^:Ty@ID%ꛟj[NKnϚf +u`}qL7.VnKNPAO 3W"nq^#bY@/ dSdz=WC3ޮеx{<C`3{{f<ق4ik5>\g+`J*"O4)W T #ea-٥‚i(}ڤyVZh +VH8ʊ:x3Wlc0w''VdB.`׻uw 僓G5-gDAFy`3mӄ{#yЩ%C\$Xhg%gj R*T6T|' cyptGNiǙ^s֗ۘǶ4StHY" `ѽrJ5prT:q.Lh1EXq)/՝sqLNmcJ)m$j𚴮]ideSz'~ʵwYa7|Wu⫽򓭑;Z#crdžh+;Fprlk+$Z nw?h½ܯטÍgp 'VcVkNFΕ|\,ۙeU';j!8h~f5}q:(c+F?bH+ߗkϚM/Ӟer1Atoz6kKzҝ|_l.o+kע _ T]63eY;_xQ:ΰ+P>9 /T.~tUC˚=e_U'"R~ъ,Bʽ\OΗˁ*m?> caOU՚ZGeE0IENDB`GSConnect-gnome-shell-extension-gsconnect-ea89821/data/meson.build000066400000000000000000000043611477177637400252110ustar00rootroot00000000000000# 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-ea89821/data/metadata.json.in000066400000000000000000000012751477177637400261300ustar00rootroot00000000000000{ "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", "47", "48" ], "url": "@PACKAGE_URL@/wiki" } GSConnect-gnome-shell-extension-gsconnect-ea89821/data/metadata.json.in.license000066400000000000000000000001651477177637400275460ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-ea89821/data/metainfo/000077500000000000000000000000001477177637400246455ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/metainfo/image-01.png000066400000000000000000007275331477177637400266740ustar00rootroot00000000000000PNG  IHDR8CzTXtRaw profile type exifxڭgr%9vcZYlv;ȤX]E|k@?}U\&Rs[lCxxx܄zP{x:7>>~>n|~_AB?wv޿~}/XJ\/xwpo֧VjׇݟSBs珿߱}E c+F.zり'vw~sV=g?wc&Rټ7/2ܷe&~.OO'[dsgל'E\w}߾}⛟7)Q%(Hr2Zaw?ϛJ︘y˟ uNlOXWM eN*t{ϴ~}) u%Fr_nK6e]YX dfsı}~_rB&9yOq>aDC!5-tc~JPO!ERʩZ9SιdaT/ĒJ.Jƚjjo ,܊i;ڹtݝW>#4(6|fiYfmWXʫUW[}M)λZ;ēN>Nڛ՟Ys2Yso֔x_Wå|\ NrF|td(Wlu1zeN9 ɓ512}efR.sFȜQ5oQMP1l;y[p<A敹y1& qqj:>l 7f\>pZrgIòg'Ugϝʶk1 Q.}/Si^߫Vux;Z4UOCƈ~ts5O'V" ُ̞)6 ڤ{&7>:azj&2v&K6.]ZcQ F[9@Xgu=kYQ^I`>Sv4x29Z8zQ}-v>A5mgmXt˹)BYMFtzR/{cԎ8}byŵB Ei KDzoeR>~9ɍDZyYkBf"? e&\yw؛?Q^gT=Tbb]uPҠvprzsD<{7wҬ@ Ec=Zqnޤl*zxsBx2>4ܔs%@ꬮKegka5Rz^|E% tE5* <ͱu+fTs=RC 䰆;pť-.LXWPܽ[hi7xxR:\TgHs9w>~p8I^.E*xҭ:hZjH{n~R5J&Qs7D#eN xBKv.HBe|CQRŜRōaKo~ha. z@nLnHhY9d3"-{mdM'u29"N {;Z| T oCqf.B^"$;*L,.ye8prWeqy2tw)?B=9N&UFpI`ೢG.k}1~Jf=WU5.>$:rI]ycS1<bv&8e=,$kIAZBu<' 3ZEk-aRȨȀ2|jȐEP^$(#(2Xz!'@`eDž i-5IG(%:)S$ m r(pcT%d j rQ0F}tʶ >%43*`'WI&JCEioA>$E+$rfT%pl#pqP I ~"pBFnI'|ZFik5vy;Es8t6i`eǙwRP5qK*B9m+-F0Ch˅BV, Ze ;! ^zVBE8]1LE.;%CN6O #V  t O 7u 3JZ2BFBc(2DI"b iQrU|:t5袐, Ҕf&1FWFFfI꠫ Ee|&>hq|-'QT ʙ""C|MJv<~qj|RwT*f57PݮP#aH*9>4Ow({BnrϬjFM4!?HWf=ܫ)wSYTnoFS-]ɍIz5ЬȎjw7*E[ 0"!Cm-?PIFD䧅n83pP*ڻ3i૜8FD\MT<-{E > H@pFL GfqV;n|;ƽ:*`>Iz)GOJf 2([]xbZTxbovtle)p{~Mr 3֗^$W,o T;jn$k$RD&,!Zp XԸ:xj,q"4[T'pin jB)jam h,;ӂ ,w@X*[u3NMer'1t D!3iQU:bm+g RHCe\b`AmPVBQQ0Dž;UOVl慞$fJ2xZbaNjp7K0$-%KQKP/ ([l~;6R+Zx, j5(wԩGim =(xB0s4ۨ.-' a6T%6`8!b2 d6S")$gh )cfsMwMp!`|''V^6EL-5?gL%]0h"msOkyF1l`O1$xP tcYkbְ1H(gm>؅)teB{DXģ=bH5"5q18WZLuFC,~ E4դTD 7U =3[DSz)Oͪ-MT=賌{#4 1oxo׀{TĸDZՊQ.i>R{SZMlBQ{iJ @@̸,g-+T ?RHw:;Qtt 4>x!fB4dё ~^[#'S&OEk>T Sj -QXhtHVˡE`<(R- w`56VxфOPbDV]WcrˌKs*d9WypO 68buSùc!zі~MTդ8Df)=X)[Yp%юgukR|;q  )wR}q48R_ JKSS7 \$vGACEsy|D{hbo9u(Kfz*FGY<1= }H[ytPoAOI(C&oMzZ {C PV^{&q\=|2Jp;S0$ /)(MJ'yt8cyk:`Z! L>0B9O9TDkKMozvMpV>B0!ѶQN ֕!Zk⃡/Qӧ^T6fKHˤ!Z4-cz&o|P,dT3HJqW<4/ 4i `{RJ [w:.LQ NG=e\^DS9GQ#S@\!AZ C<9=hA06ôw1M'qT@3M=:qLCWpDf1 6w(kkeΊdC!6ȢvmC岆j-_z,,Ӵ- q2Q&/9N,9Q 5" .INhڌO·.Qwak !jT.El=4lF D][nv;C3O wvUڔ=Dr]o%x Q[CyaI8%l"qPOxvkLTWRE{˲tӆEIAt2am8LEFA&yxKB'DJ瀃ޒ'ƁH;,=GΥe@M4‚aL5KyX!i J\wϡIV,ޠaViuU'ΐHrEU '-E"艊˭& çkNMx~I%H<7cPow0ip4^\"ׂ ke>iܫt,VJ5sSRKY;~ S|\n,Y$ Qr/Yۥ StA )KމC[1Mb )XH7g<"Ҟ(oֆ=`h~49Z~+tzhsW.]^.Ao` +8(h2q*ÃVhDCSDąLh]$ v4C{C't4]BhrU,[Raf>jE4"FCX4S:DŽii ,r#h侹z8RʐI%MAl1n7z_cpuDfTP6QIuJN Vj'6>se iK.˲(YOqRpӚ")kk-?,Toz'k6d3QԞMbKGDDWh~ pHYs.#.#x?vtIME 5& IDATxwה.[`^*M"|hԘ ֟-c&vAkb, MAA@H,-3.pE{fs9=hiL@DDDDDDDDDDDDDd^B`lëuQ """""""""""""dUjwUa<:ulG1-4 d%Wb嚭5"*4A祊WwѠejzQszxxQÇPPPȬ9ΰA1u]v[cktjߖeWbV, *O0Ro@>4y':?xx鯶mҧƛU7L+],Fsڴm;z_Ս[!IEDDDDd=+hӺU?ؓ,_󱪂k89_ґL `<-^”3iAUڦUw;LJm,֖]VX2Q^vaq]q<&|as91 #;ӴŔF tE+yd&\1e"Y$`&ITI`k;z t?s?š?ھQ4O=7&<ݚ޿9sJ6YDy[6cSq],{x^:톟ʹNN-r׳j>| \vf] {̅\ybo䖰z'tS&Y.̆.oU,"""""'k^H&☦]yǟN-ɤE?kӾmڷmÑÇrY9Yf +8.^eO># qc븼<88:p<7* čEG;F/1<v$۽ܕ~v[MeG2qrǏaXo  x[=%Xk0K'U xf-Dj iZNo-"""""ޔph}!~&Rk$ 2|/UoL%yۮ+yq͋" ՕsM}巃;K6}Ygx4ƈsٝi^ y Wg2{8טa8(g:I\RDDDDDm0 1MVeζ}XVTOlذnM8Oҋ/en.f:N-#GӶm6?'lHbsYd2W͔|!tq 2h TmGpW9g|dv5ʭMi46i<%uǴyĬ =9Viv;s3=(c>;0_7KߘȔg~C_ÁdsZ'>.wݯrm. 憏'Uu!Th/<8>|0p= 3nIx{^y4o6ȎG'F"=$mF$=&i0 xx72;͜$c/{c{` 7\֫:?}a<N&u50IIF2e2iZ$<_Ɏ8 \}!7[PA| p(Ef9K?ukPkG&za""""" -/UH G 5'M'2kDfMȌ/>nǫ\㶖 }尣~E^xWcAB;u?λoA]v8ts疈Ƕ=SO_? i&pk{ 8bPp938cp]3;U8u<%59Y+ \Ǣa[Ȥ))j&lBg/*z8vwzOڕ))Lq*L\8˙<_oYHV?؏>YBhKQH&Hx8ޝLęK;u$n|h?8$ZJHpW_y9i2i:|AC94y e׿\E(N0ag'm;uyiAyy9x>N~t?`fm;u zs^{m3<<><G:aI˂rGrx˷Y&d.8 ՝q,>?'U&wGXORi&`rA ?sp4q*K `Xk^9jû2קd2;\U\^Fh΢sce-hm:)o! #,DDDDDD. .M#p9SOړaxǎSXn@0:8W;t!/$5@0-,ޜ9Ij[%5 #o a8XE):(ѹs'<,\O>Å\N !MV2紷M^U}^z: 1- }yxw߯ib&ώ}x뮽z.;P8`8e**0E* =r\!Ln8acOyYǤ٥DDDDDdOZ?V00M  ѯAd4kFFFOro{,XH=XvX,K7- suWyŗycv Fj*_P$/=zÏ<ʓ>O0篌[=ku\9e`t6/}L->;U 0Lu1m;Ājea |ύ2G3J|8 le[41v'빔kd9p䥡Wy@#wA:fٷm EҰ|16͟S/bkЯwz0n:;ɸ IJ|+oZx[dHƕw_eĢ$u1cU#{YX,7io> :S*oHB6x Kx>~ז.oM&VP쓬\ۯ5 (}ض %'ljy@t"/ 10u Ƽs=Ys]qZLc׾WMôR;icIB]sHliϜyә\r'r23G`Y0e͛S<50rQ]-wcrs1:C"""""R[u=uq giKn֖mF\:[nj֜5 ˶w奴m С}Ti5`n23O qxҶm;0 SE]:"aVE` ?lpP9}6w)p*DA|9OC[ƪ}е<`o>y~$Xx>v㕄ffsEF ݯ/_po>>] ,mf6M;4{fFmycr?%c%t]e`|4qm%3w^1dGEDDDDDd7$*J,^_֒Ly`canŴegqpUEjzw'gɵ])m<ܵkw )۟K"hyvQ۹' ;9'-_y>fr`,%w L0c/Cuzd'f<].mFe*-"""""R?.fώVv<'f(}2j8q gSfX -N>5amO,~{_}WƒPN=DcCM$kӸC\ʛuM7s*;f&N<%ޢLeϱ'+;(~uHkN8N0kwxwʗ{>ૂ6`'rE3=  G6"E$=05WTh?5ŴzZG2Ud"LVO56V}h^kTG !֌Hz&E{,[b}>_ya,YR˫>%UEso|k~lۏiރj1ũ(+!a©śԶr,ۇ?I&q$abC,& V^:A0RQV "iA'IEi12L&I(/-N a|>Jg8-xxy)N2 F* UV]!^^F2u]LV.""""""LS/PkUD=ס4':Njm* _T<^a-XE9X9'bxKBr$ڬ|:?uK&IwS7/8IdxM­pX0LU[]9Nu1:ײT.yI10o`T%Ur3'L=p\ٗiY {2u[=mnOsI&mSn[OIT/n[u]7uޕ^u-EDDDD!y$Hc5MlX `LjmU&>AzK"h]: 빸$r/*t+kVUުy٨ؖ k/"a4pd'XylzWSryΠzuu_׷ `U>jz?sͫ޾6%"""""")P$*N<޾UnWnz^UXM0 f{9c?Ϗ4(Q޳9λRW뼧^-mZiPeuNYCYαi;vSuH&זu-r[<146\5^(AMo1cv5QӱԭOj>cvil'rYZ儻|vv""""""γ~rziaL,n75呞eԘ7qZ^rz}y4S%7VdڣTaX("""""""""""""wPXDDDDDDDDDDDDDd/y h M'I1 ՆEDDDDDDDDDDDDD#sI&┗SVRTM"+5/H4ra"oX8""""""""""""""M Ek0 p4]_i4"!""""xK&V^8 H#dZPfmma ^"|&YYӼt<`lPA8{ E?~ Gl0F<2L+@F^ +g 4hն"JJx!akxEDD10D30 ?\FDDD dҢҚeiT)`;-I # H Gp1ׯP0DDDDM,?f͈m]$%3˾FiFn;T}^ՄTi0W+""""MPq:V8ل#;? nт (oXEDDDd(Ų-BDDmBDDD)ܸ5KynP ay \'aH0 \'@4!ykf1-rs,}~?9lX#x-vUP+,"""""""""""""{̺?P\,J˶iْ W8 ra||5ADDDDDDDDDDDDDv;Xx%֐ӠoӲhCizV4 IDATVHGh2w5M9lڰ3>h12 86]I̩UNqz,CVVY ^0 sZM|bY2]" """""upGnjH$35}'"""""_̬,!"MN "++ ֹ5EDDDD꠶_f4_@"҄B!beu~ """""Y$~i`!ZWD,""""ٶEzzvmZ+""""""""[,"""" ³_ iNs'pp~xy־4< U9OWgH]@`&$pu3h(C%5e={Gq%F_\~ѹa^$']۳Z9ntI/f|w>¡Ro8XVLϼͬcv9ocF] y5#m , Or=8~Bl|q1+=sEҺU^xѺU. x `&qGLC~;,ۧnFst(ϻ{6SUйSR{ }}isj<coeDxu|tU\pXĵ;~w;YJ;zXȬǯw:3ンkZ|+w^׿q]*3n5Fpu/1Sr=w׎yצH__DAa=1 7ѺU[_PcOeS~$""""@*A~AavtrsbV*?dgPw[Kj|~#onpNIO'K(\=ǽdj$1g2C5@2c0C}ɤYNGxy^񣸤Sv'Ϟ|ˊ(\3{wv5B1ϙ_w1o] %һ?a=Ð7v 6׾CS3[^ϊsjq .g_LqH$LH&]@S@|V-s9#i65tg_`O?kH&Y|%wA޿, RƏVO>DV-1״5M\m>8,=Dƕ/{CY>a2mls{s2o\?ۋӰeɴl?qgZXoi$4d s0qL>^yc$mMC8'rE-"8cO._t Hh hzY7g'ذ1,ߎt IڤNSpРԢk mW/6A24&tiR,1{ѻ]!K6^8o` N>Goᒛ'S9%mӊT-*.'ƲjZڴn4 """""0/ذ1eWnÂWNaY^Q((,׾ﳁ&V3m ܾ88ѴVgr |~-9p[Kftݍ~g&Eq& ?9 _. <N.y7'Lҳ3k~[xt9JN4_ NA ~؁,t%+%JDD~ W]7~bKǹQ H鎇H=,Zt]eE Om.%ӧ234a=j1+gGr8Gyn|䵾-1|=;fڷ{ Vzx HDzSXDDDDDDDDDDDdkѼ9~!"0wǪիq 'p'2?|>#9ͳѴnEjjne$6mRaC0s~n }1IAFC`=$Y3uku,Q:7q׸.sfF*232o+X(,""""t'] !r*6Gxk Vr/. =3ײ-u!P\槠-țIqC"~|e .|'~#a+W8߂ *RuA[hJ KgX)պ V N ?'O9نΧim.Ƣ$W1ٔ{<$sodSp."""""ʸ~s:XD"[5l(Oqy?}&3gdgضi6vUi_7,dI3PT h%)ڟ Ϡ<;鉯Hw{ Pj23'by %&ZE /#ˠhBߣIhc(w;RlI3*\hcvpd|䜗lr;/Q]I5tyry5CǐK~X4R:wh4m%%%}NY.=7Xveqo~-#G)h{1qDdl3xcH#cioBaDFGW}}H O4D" )gɏoF<"ߥď"BeW6CI&PRàO)l՛[8bZO(. `1i1TbK"'U +c`-91yK~A &p;%OP&."""""cU HnAEW dPjF]<%??X(D jǩ?"""""/ nfx%Xv2˰6k#`,+[X=o^o9'-nɨ9&b`n>"3Qk[b[Sv XfOm

wmEZ4oKaPuیN-Max>NݥiJ6,%֦+_ưVםo NQ`ߚg`X`xu8'o ʑziqpg(jw""""""[;sj|lpˋ*Q~qVYZ-((\# """""GxӷwMZv· i>zJf j͍5:|-FoHOB~ja# cU!͍exe`;5C^:9 Qڴٝ9޵k;}6mRRt"F<3,""""$X8Et\o bcR@ E˨آH-Y$,ŎO]w,%N#0**2h{0!vI/9뎢)@eN"+6@╜Mqs ^%_A"onqgPch蹋ȾjoF725˸y( ved:+I}u 9X֎uw{]4m4>a`v!FN`&s:nK 5VAv6^owCzڵSiz֐a5E^mG!Pl#fyb};[ߤP?Dכ0&VlS^Y9z"""""廨nw>,XAcHn9r &~LFY'}/8>;vWP]~yƱXEDDDd6dU vDDbdrӔ|:w6mXGCS DDd\]ǎڴnݷ/$#5:R?Cf&h_%??'}/& nb:`⑶cHmH V^\@AA!7]S|u\wFd)Ljz}v\}G"PQ F.yCMK7J8yf |3{6E~!*lGvv>D2IJ,@`=8aUXDD$0thߞ[ok?}VnF:oϲ}n\y%th~+b1^} ^{?CdSXDDDDd;>q0M4vs L:|>EDDxsn󮭦nl݋QL }ƟhݪQ}"{ """"";`eaFuWE`=<<0 Q@DDI+((nMjƎO=ؿc~bƌ$Q4MYQ]^X}@qD"_PXDDDD֬YÄ&]6 deH7,(.ظ)Aq[5WyuV C22qM7gsUpyҿroff̸ۗkmwE.69YFhp_S?^Bo&3#}m_n\4ya`v!FN`z2k˳9We񔕕Wo L"&7%9ꨣ\&Nz{8*""""""ϝGѡ}{y}ߵ+wUx<ˎ5di=v3>g(>Ox$cg'pً$2@(2(P62.P--J {/;#=$Yx*;>-Iwz,= w  ҂4񘈈a?X͛xfo"##={z@pK("""""r0P\RŽ;yǫ4{gZugΝ`ig5u_HݱʘK/ra`gXnY0qL8=XwGCaz+^/>W,^^^>.1xV6mMN>}ԛ̽Y\tDa/CYZ˂kgP'I& uPDDDDDXRW[XX|rfMظiH+XDDDD0XLg*]nE8;, 1?'d}9'=^99(2)-׌,""""""Ǻ]z*AD`ôv3KJ(,I逜|ö>yO}Uccա4-u99LR#ݼ;;WWbye9SiWٱ׆EDDDDDDDɩa*8ykV7T1oxf 烐 (ΪxmiU KtRHS,""""rM|^#Uӫ`.2c44"""""Gx}qG6epŨbq`9+*Jk[Տ7n*QDDDDDDDD`#d9)bsY'bS%""""""""JH+X¡*eP,""""""""""""" XDDDDo9.1H˪9rx"""""ɣ_r7n\EDDoRRDDDDDBH=ve9!cG TYDDhxcwZ*CDDDD tKDDDDygX )v`מ UH+fe."""""Q^^i˽"DDDDDP,""""BKKT"""mvT"""""(!: g%Ū 6G@2DDDDDP,""""eH IDAT9w88(`iz=aq˲T1"""""@@DDDD\8ܽ;N0ۧyEDDZ#3pbzݱ_ZJlQ͋Hݱ ! """"""""r45KWDDDDUj + d𑅾 EDDDj\[ݬ0XDDDDDDDD }M-! EDDDDDDD)V|y"WDDDDmpeZml""""""y^U1 00PJ5*>xk~w' EDDDnZhkXۻ~V,""""""mApᯩu'u[ӉZiʐ/׫Qg[!4VߺSZKM(VʲhZ~mZ%v(\Ww X!4!zzC_5wM=ŲnTXVVZ\wXDDDDDDDDA`S_Xk[V?G;iHז=H&kUuZCCX!4TpsZn1ذSڽ~M`!i _M큟M:kWBQ-Uc EDDDDDDDqZ=~k-? 6u ]@H+WܿTUAW~ lY EDDDDDDDDy\;5 mؔQɰA\XUZ5pOBxc!z \kߪUAqS &XNTRV \+-qU= 6 ,""""""""G/6 `[;5k >_9Rʽl_9* vcH=""""JYW, 3@΀9з9{4W/`Az::5vZMYII=_ SNSDDDDSH{3ؚp, 6U!pHVu[EDDDDDDD=soeߒ|>^W5-""""$AB|Gy9 K wrınծlƶm|>8{5\1'p孚& 0""""""""R!ר= teߊKRU(ʽKIxT"""""x8I_ׯ=]uvj7=orhSqrh]ʃwߪEDDDD{n.kOjMM\94f2S=/C@a ^۫FEDDDDS\{ʽt+Pe_D-F .SZKU=l9ȌIMM!33 "..ݻcYu"""r*?{Koô:heddp`|5_⟊v֬շDDDDDDDDDSglj2  U@Wgz7Baa!#G!$$䨼㏟xTٺu SgH+h#nݚ~>6)~LAC-mc ƿgeQ TXDDDDDDDD៩Zox:dff0d0J)))>* W$V^XDD}c~>6LNN1Y ٿmUª Z_""""""""r %*~zۦz~y<|>A{ uƉ6B66/ !זUT-˪I]@׿DDDDDDDDDwk`UA|>ն6BC0_rېMUA9phֹ_0AWmT""""" fVjJS>-4@=JT5/.I5ps\Z}P;*Cou^F|UT !-""""""""BC@W=t!MM"""""Vת Lkg_PN0aCң[7!%5%˖3o&Q'G Wl_y|r_y~.WiE~84Wy,r1|lH]WWv3˜~ʟ~SN~^_%۹}W޼zc5K;JPwl=5;pCSimV;kǯUپVʁEDDDDD&ù9OLH 1!q套'W_aru/wݦ_|ԷougZsGDDDDLU[1E`;Ve XDDDDDyчIU9]<bc8` |"+/gԈ_Uy"-hʹC@?5Ԛ|.}CEޓƛg\uڵ*]9ۛq^ey,ߞOqb6e` uW#FEp޶F؛Ň|ʒ#:*ÇrءQ߿PG:5?]|kuU+"""""%Gnn.Ͼyy5y2wcc0@vv6Vᥗ_%>#7 1Kk .733SGY`ϲCs{_ΰI EyXǞ|e99{4{3C0X yH{ %gsN9K霘Ocڻ+j.[<;r3]kJ礍qEF+?_Pϯ?nD~!\W^| ++y_yLP +k9w_.? #-=ǯܻ{O򎽫Lrpd7uܪg# ̤sbbD͘ə'7ڵ8aN'?Ξ_~Ŷm۸ˉj׀Cwdƣ+/W&=V-=9EǢr2Sp?ͧ˳wX@#bI_ͅ"6iLk{ UW\Fnرsos籧!c߿*_|)uHH睃.o}8ZǨYߠ#)/ץ_+x礤У{w•]'}9j\Te? t.RɅavInxg1+g͘ɾ}5y@[7M~^>\Wz`ޘ6oGX)e6 =R8B wJ 2ز<-+f70(LqDDѧU=W(,,cX_1y|gkZ|\ZZZD;렭;dޣ9xX,엞bٓ]@$7 &X,nօ1Sn "fy& o}6}9}ڧ1lLn] u/Iku V|'9]kwuV_,`^p+$ t:y@?_c /<\OS_|d ͧmߟ^Ǎg&q`b_7-ՎNfR+_潟6A6@@%>2sɲ<ʃDn|G銵eWp%9e\ĜtrUQx*g^n;eqweVzt޴ۿ-7tE""""""Xw~_yЀd HE?hs}60=^#>bCয়}۶Ut <`n \}1 sWYkvYũ ۾yW/ru?BZ;,':ϾW]_ awx'xk28(cwsq\|ޡ%~ ذë"Y;8yIM%GHuD~AF &:ck򞥍y0~l}˛b>9} gSZ22rݍ7m{ 7v;euv:uL(9ߙc~$_N¿~~ X@r.e=;SB|{GS}_yќg0rPǼ.;c8!? ,w闧 k ?׀}1_}m-^I9޺m;'òzhYmӥsgRw8{?=yoXpl'/y63%)8-B;dq}q2{2nLn(^yS|<11::ZGi/ø1?_f"D[PL.pCUs-r1D5ҧ1NnLn]1M~,+ ,, |[7cpD䬉 +{y ?mfN'͸o1ukl+,ZkRY wuuv`@8$__>t9eY(۳r#v('6q?bǎҋf\05Nc\oCC`")?xRyI?Ή޳?%˖p8tiGw?/RBBBx> ߢ4h9Ttξ}tQ=oJj*7z/=Qe.""""""""ͫƆ>c xk--٥]Y2;KE/`V&w8pZvvIOٲ&y]S qש[l䦰Wxff6Hi^s}%|5w/~M?`_`;i$~[\147˜a !&2&1dfby}ط*"Is9[ͲW+`#^߳`!ƈ3ty!$μaμl;ozoOpE8*~T<+ekH!44y{ K`|m'' G iL*䭺]7>rgvQXXu֦ﺧ:}ƛIIM;7^k_ 9W몇n Au/|?7=[,ik_G_R4lh!*- Z,V{hV\|NWn#<r0lMI΅0x.ugW^DJ"NӧsҙF A}x>|AI:{HFB국WDu}>z??o11;s} 6n}>#OLL1WOEEEL` |~{ه:ܼe }g!2"W^zn[4 F3C._eWA!"Mꈒ}x}.mƶm{)tǝl۾{xfϾBb9s$BBBZ~s\-͸ \8O̫{ >v=: euOd׌wZ upiɄ5ٲrߗF Aw$jw&e;.]#Y+͙D3ওbqo Ém(++z[+6GttT@XXW?ͱGl .{Lò,ʚeb1L 1`]qlcc슲m0lOy,:72w,ю:\\-޳Owe&?~bڿ$22ߜuVeē@e IDAT>slذAكX:͙t-;7eSt7_Dh6BB`[3-[ӳGw~|n%$$pTxmHKs m4UDtܹA޽I/##6yp:Qq8X˲*n[ڲ*p""""""Xh]wWEH+5ѓ9ceŜ>8޽W]fު߀zض63Gwl""Α n{)~1M܍y={v7jyKQKl׮]:EDDDDDDDD6|夥IdPAcT""NS>W_žVmuZCHô`lHM^!"J5uHkA8M_FP`DDDDDDDDDڒ m[EDDZ}P;NDDDDDDDDP`9q"""""""""m4q""""""vD䘣"""rTpDDDDDDٻ*ADڴ+V41-\.q:qݸ\A:KEDDZQrZ}9^?"p\\<;vСCAA^vٺu3:DDDZIvIn=[}^ ?"pnfԗm\Aӭ[wq"""FRRu֪0={fΜ:h?Rb*oc'`cWmۇmlۮ0/?}iN'"=pTtbYVmUQXe,,Rg :T!"m+H=QqDDDDDDDDDDDDDD~T"""""""""""""Қ$G޽q8|>oȖTIhFEDDDDDDDDDDDD8NS|MFEDDDDDDDDDDDӷO^u;o.jUS~O?Ngz/MFEDDDDDDDDDDDs8oS|ׇ7q){'e_ƫ{Z]ݕuy3] %rMQTGGWs?`LRכqeO` t`B{eXbJ(XNJ5d;ܿ3cߖ,Nu"8]ܷevuK8s2֕:go{ K& Zը3Νg"DU ..^Sb/O/""""""-ؔ);Zxu\8:7 _֍ckKTz֦:3$w62s6;d$C$=#=DJ҆wP9""""""""""""*{ǟI߯~F{&yno 'Usn˿/8O-g #1 53ßXVHkȨB?f]̟<tω.}>\HKy`E"c/?.t2vȗ/ϩ3vw8y\ν%۳ȶs)™]H\3r=OIGt&4a+L]  ā Ⱥmn|8ULH+ܿ0~{rS¾çn/E/no; h&_0_ŝv|3ΘًgIvmb;&dEIUw!~"78<J下Ľ:7wQ1nKQn b Su,\11DlĄPґ(^vE3);%AgpFd.gъ,`=}KCo_.)9F,'q #9~bC/Ŕc-`ceNHZ xF^yԇ)E^^9Ndٔ-fᬵϱB0yl(Hg™38/M'6511}!e=J*[Ҵ13{)Kh:{12:Ջ Q#Fp\J?xްiSR;j$111M|> rйwŽ>uꊈ+ '\3/LuoT|ǛgT-*"06,xG{%o4sɵGffMa7sPg|Zy"O;n-=|Xv.+盕ADAVG:Df}i:-HBąsY;+NWaӋqHLV+؝l֤iL[͒Nz{}ݪv_0n"##0aMv_P,<>M%:3wm̻;ځڟO29 ݬZM{),qוG'&S]exL RUrowvtdg1$o/\h7\0M61q;f@P DfBgqo!nfh,H7ߨvm\y!iZ}eʨs2[[JE%3 z|Oym?p'xyJVfȄ~*C+I[J=*ȶTߞezjs$&c`2g&uafum6X7 ߦ2|Y-IͬӍ~U&~0kv;n Ew̎5&EK"*gXXea3.*VT_Ə4~=zccXrOV=PMɶϳ1Xct`l}R}<77 E 9g)6h$Wn.Nr~ ]+Y>Wͅ?t,'QNIz*{2DNsc39c~ &Na'_2O.g>'2S\K`va` 7E,]^O ׁ:߫cY,XY_P,)!e,:gߒKaLT\=>`cOؐKa$C7v %g6v+(:̈́! 5s4%Y%vUbQC1Ja[ffSnj6{#3to ~#v8SJ9Vs %%#"JSְiXWGZ`~:2)5aKY~'Y>#:spFX3ˉ'u *5b_ 61ރ2.[+|>/雷#1^}&j tr|>Glsfe}/Ncz­C|֚-^ʺ{WXv)gP,""""""MvCygeV?yѐI޽0lqpg`ڕ\: ?WHwwj=j1XǛO}|KK~=sm~Ç,g<*4+p,z&9>vI۝#x+a;u vP[>ԍ)L7c)&1;n]i+5tև1o9Fq鋾kpD&q$BK62Xy1e} X][ֳcHeR("88S. 0[Y!>,NB`7{׬fǀQG;L: As ـ6'ظ#g.73!H_VYj@n9kE3xs7v9m>+EDDDDDDDZ>e_BHssDw> ;~S%s>"R^^:`Kx^FaXpu%g0&EEﭢB Eit{6ΆM[ؚNNL8od̏ksvd)jy 7enVAQI$Xî;X؁;g Ha^7n윽d Vpgݛ{Hd7Ϧ[>rm@NB;&e y*M9{XT|TDDDDDDDD-R,.8kٽbͪ ՞KznW'>0u ֒T]M%lGEzSqQ۪K]uU '\ᯈ$,.qI3)74u<nfz;!={.\tNCoV={.h{CG$c3M :>*Mꜵ*_9r?kEDDDDDDDDڰVcXp!g۶FIL0QFaYJm 4vZ x 8f؀ބ!Y|dѱ8+<'CWVH3BEu='&,3&zeg<«rLn`9 Gl?2?{3[0Vy@Exq{ ؎wneOn6+­[I +Q!:^ [ Hdܐcbo#ca prZ-b_0 Y>kEDDDDDDDDZVݻYo.#''EҧO_nVbccu $~8w`m]"},^3\|VdC{ͅ.KqY^ܞ>8u8Dav&^ImaYc Ʋ҅``t18YU $lW;9^pZ`t@vYلJS@E󪢁5XQo-:Cvwx@˕Фdn* Ox{iҊap`E4[5I d2sG """""""""U ˽G~~s9 2(rssY|9}7o{#&&c /&22% ѽHn U #8m$+'"G!ѮP0Ɵ햱ln p;\Ŵ'ˌ=={%C~@HD =1rH|11>ʊ)R~ :`rZ+YΊpp8 &,f]dFē4`$tl3wUTLEhT 1^74[蜵3-eSz>g7gHdzњ cc*n`cWmۇmlۮ0/?<1p]waÆs"88JKKygYr%}G9p>|>.Ν;3i$СQ=rM7r7l#Νg"DU ..^Sb'"=pTtVtv8pX*{>[,2VevG9E3-⤓N?{U6`wj* IH/@(!A:*e 峮WW`   { BB*${DIAߟriS2sx9dffұcGM{Ν;i>YYYDDDѣGZu~ŋ@xitx&O~ɓ`̏+ !B!B!g(''G*A`F}.l4:w/ͱZ0vSmի޽[j:WӴZ( vfqΒJBsԝzB!B!h0޽{3:/½Sm3fKVڵ_{aڴi\|HZjō7/dW9((k!(( &0mA(,,"..vzn`6Ӷm[+vaӣGw M_K%!B!B!BEţNf <\~tؑ@ٲe #;;w}g5u}ܹx/x晧 >ߟ/øqPSиqc>CNJpp0> ڵ})L4믿???B!B!B!hUnj gE7 .%%%YId IDATQ00t tC=u]ut]QRx~R B!B!⌨EeX/C|pPٹ xZB,U DBZS*B!B!B!9c%̵b x_W1r0pnЯ1lR|g_ 2;Ko,I].O}z}yc{4i-w7|WQ^u]Ikցڏqujv^S rO { w*J{%c?ra#=!jd PZۖƖ,(_Tҡ2!B!BqF K(Z=_ڍ/vj6fTr4;>᧨xv|+vx/|{&Ldӟ1{]EFwz(z?U_L/|,zVnMfk骱\)J}rcLՑ'ąNF g8N7ia s݃JC !u׽h(4Sz B!B!.lܓ\wX;`%#A6ݱ`#wD "֜ů{YjZunm =GKg1o F0a3nX.+@>^H:R.+f "~֗.,L&!!3@]Md.;>:ڛԊPԿ{6GޑaH *EYNT"EQ A1B!Bq]0aYNh?~L#pX Mi;m&Cf_BLX/WޡU,iSQ>Q=g0Bt2{vGZ'NN Af㜤'ąFB4@TN=eիahv4,6HtYz 1t B-0tT  !B!@|si^ 85\+6g,r~>=q=a |==o"}}p73mޟ5y=1Ys 5LmQ4bY$x(^ۿSRn ݉;w'Fǚh{3P ˽_35ԃB t홆_bB!B!. k+pn9t)Z} =;|^mJ\ 5v^>y} RwLt (>h'hJ%Atl3z;ɏ&6"5Y%Ir*ۖ dPhB[Vro!t[sb:uKz;m?-`})W@ c|XuՋ!mǟ<|Ү.Ԗf^en?#HImsS|hܺb``9n\ʢ%>Y5+W|x}2`LOykvRdlCs*ߡLsF0s8d.N\:z1IԢz?OX'X):M ЉS@hsD4l|ݯ&7FQ 9r0ٴ>tcKYKtM*C!a]Z}M`!B!өX<g(ަV,T~fkx؛^JeP"٘R߅!]Ivå>LJbMZ>)#}10C*v˨s]s|r,vW%AčL7S>AIuzώ\:oxw#<^ѭ]9+#5l.b!?qz ؇y|kΨVwQgj-n >r#KH*_۰A\6ۼYV}-1bp[`ȁ n穾-tÕ(4U-Q;SN?߬F|oEnb7ZǛh8U3% 6 ;&/c0 <W1-(WYT*JńUPF3,WT_aK=!ğ.B!:jI6 > 6b7/1'ؾ2Px(O//}wfYK> 3xcdl]/ۺ2u#Lvnx|4v=BsKN՗"/ڕI6gk['O_'(jLHHP6}xwėؖhNM3B:H^{0Qo҂VJN2`?Üd8LaghGm?SJ: oS@7㥺{z岁&yqvֆm4c'cp.Q]%촱_aT>B? ƐB!B B?rsYŬ;arp<4͙xb>adč@Z̚7g͛xvh8e1yR>kKOy)e2^O|@tਚXur$-GhONQ^^y| ŨtJfӖv zNHBLnҏڔR2(5<.NOpa4탛wP(P}l;Me s`B/b_YxVu:x "QLjNC_fD 3#ZQaE:͡]\p9i| ߱c1uڱ+pM3Ĩh@B-6tB-nh(&;oU@]7=3yNxQ&tEUQ6,+!-B!B!kF,(67j:Jtco҈F*n ')4K)ȱ D}BqcRPDnJˡ@ڏ7#ۙ)#$-# S30`"5cE0†Ji1e5=~'t,q^ \y-$"dtvjIc[798b/ҧܮcPNNdbЫunV+s9Vb~'`!' --``lQ/T?⋁+w'eVV3~TC14MPT7?b_*OEmy=#pո0Lf&bB!B!B՟#agŁ}V8~/[JHtk8t؏҄ •G(]`(ah˱>XlBhؙ%4;Q wYW۶7.B:w_"-äǦH|RalдBsv_+HfME`ѵ0[˦ygC(9C۶/S@ݴ&=5".t| E1c%9A=?q Upsf\f;jA})+/'Pŝ5|=f$#}F2Sa @c_;fōI%͕]#bwƌjM WΦyUIΕD|a]kq+1\e.@w'Sp%,B!B!7ı7q^ٱ_R $b䓽y), ǝ=+)ڳu{,{u([7` @ϯ";.[ ~J"/z B\TŖ$'q ?z۽X@qqMWQLf(X-PQ\qQS!pKPU⮤҅t4eebe9_Ír~C4"DȂO>Bs Ôۏ 7l hwinnzRZ6(.3YI섆Ey# GrU;y#Ӎb&䷡C;̍iѳx)vW .&)w8(+(-s:554TLJZ6ϊ황F/-@8OHneݤ`٧WpN ]nUl6<פj]S?(ٌ W1&w 8cڅfǖJæVbV54D Bc7^X1& r 4L1dK'r`g#pi@1ӱk77\Is{^|3]{6a\ ;&ƺGǍh)Opjd敳xe ryb\}16J ;y|"YUS(#}1p_:YO1hG3J'ruf\+o|(oV}vuS7+ʕ|M:kݫк_SH&jψv<ӛP9q CwU_J E]^?x9vf88c i FIqMG+]aST]jj:+`l4pRyBk$AV!B!B\tf4 V/]sqw_ʷ|⍹2eMh&=e`U4,S?U\%\(>6lVPJHEBSlxGmP}6EFQ.%una?N /~ۣ7#4<0s!Gݞ 58P/=(Lbf=ë(6l6 e%2 6qT;lC1YQT ( CQQP0t?w x5dE]Ma,D^,_GyčEbeM}+_)ULn ZbJ_sq?Z9^sMDzw.iнK07_Blr/1^%v.}*q]./3kmuJbHmUbS&ziٟfy om8(f5LFQR[}Baf-. g0&d3%:͇BZU?yڎC,5/l`"4PZtUn6J50Ի˗/דon>~];g`5(3Wߘòi,5[Ϟ׫ ^ǝy.{7u0 xջs&Tq6@Q@nmGMklX J!B!S| "D<ʼh?`0"cl/7e1&ePlc7蚆ӭ9-mC Gv= W5.JB"͘O͊;f 7A^%ɻ:K]hVqpⴍ;Rpug]Gj z ȰV'] e$xUvɲlU{ryQ\śs)mM]wmaCprG9n(V%"g` nEQNkm`Yf #Tšsz 6Oy'*veZ@ˇ=džΏڄY)8::Tcsg3uar#_ u7T_[1zd6q7.yNNey9zxwEs(Hgdž|V?&Bޙ !B!/t"6!mJ~'֮-hܥ:U/ ex 5 Y}"CA-5\z13oSbdr,7WƄ4 Cy%{XFa> IOZ̢eIl/#儤>~W1*䇗=kǕ֔Z]K,ĈAeVԒ#]3-ΜԷFyEs{xO9jO؅Q7OU}:|LUB־t JsjLP c6!'bÍbG5YPjU 0~ f`:b`Ux~LNW$5ifv>u|t;2\g#R-aQӹ6$q3{<^$絠ԍa5 99MEq!͘7飢\5N-Ь{O4uWy?힡,]}ihƦiTBmS+83gnUqu4{} -lU1!}- p׎VZz iݫBP1(_ Ɣί-rbۘ߷ y~E[B`.z]Z[{!hK/ؕU%0wNKë꣚6j%un-{& *8J>]=1^UߛαRtߏ=u2V~~I\<'xun[fBL`pd%LUh,68r QTZ !M mXB!B!8{Qe̬ˡw}qHEf3WEuk=[H*h yXMii}YD3H^?O,~߾=ʾ%YルWm IDAT>ς בMSz.YsKOo# &HS%SK YUݚgnccmGQ4=wx&k9+*b$oLv[y*2HCߕLɻosk++PΖ7偟™x <ڤ JėUB5me^kM4-f]Ӫ1ö:n𶩨VG`;J9o/fkhdT >B) Lʄ{1\e.j=j@w'SjLWM%D6-.coC+}3|v_^Zp f).^'rLL\K>@^UDnm0хn!GVuӧ /thc˽{лkc|zLyknHl)k/{ |IlWTGR(4JψtJgM?5JAWKU5r%3NMPn)KgjH7=gtN;b h#wV2||afQ9_҅`UAEU&X#mP۪7# ͻѿ[6j>uGjjz>:w}+/UmYQ_҂\B 7A6(%vЕj@J8H誇O A~JsE[P4͢CoSó8PT-&u^En!B!B8ܾ_٥*$ nˊ,RJOY PZ3]o#wjFg8MsK%Fe)s;y[8w,O!.!|՞(eQruVW!q*l/_JL]ᠼg^kMow5fJpYecwtO,:IksYƭWElK!I9n*NtE1Z5"R1_^ƪʯ$Ft'o%E2C-oU?PT3US[dEw >ٸ#ZZ@SG ۥb;VVm*jD4JE-,58Kzp_0r{; YMY׳EDyǘ=f-?徫?MN˾&\`)Bx{RA$#m=ϒvЂM𚵄TľOhTW>YƶcTXi9kVg{YO;'L춂Zv W]Vu}f'sȫ/XՑT|2tLZxKIxst,ezVW*(4B!B񗤖,Vk4pAA:r]w/B-UM<)[Hڂa+ewQCK8S0/uVczIZ~]!HAF T8yܺ?yUL-6!L͡c[A)s֘aBbt J\l9XA)59bvظ5^Zn涮!<фVXA) CwUK1P㭱i~x;eɕc#PɯyO4x[SnRMɄ UBt%={JiW|a nEݶw˫'VԚRF8b,>L~r!j8}6kSRƚQal2Լ<̄)=%@Az]:;-͹蓴9ff΄j^Cg0rRDY?zzŧo*iYC*IOB!B!B\Ht>dvϸYfqHuy7oΰaäM7e=u[a`oPp%)-I5d̮n~I秪ڸ13mD3vG}|ש,fgWׁL.L0gv2sNz0ٔɦҭE+hEU.,/y۫O;yhgY5t ~I t@yՏ(*>5ՕQC)G嶾oٶ=[xM`uDh*>- kJrt7pI<1DJDMqnņ%%e0LE|NG24Lhi͛ش5{Ěj){Sx-lhܝBό ƴw pc^Btb^gK"U<2~%У2׷bI<ڭ~c!# =o57 2m#РQjFdf,fY/|7羯!B!Bqiବ,~=~ժp kARR$$$4z47xnY#i$n@!{>9u'c:%Q$TC5"5BP;y{}U~Wp+rj9ۛ=¸0cҭ{Q RX`bēw14P `dCi gۀ\L15MdtP٤E1v+%ɌmAx;pܼ懚)x+/WȤ ? YL][hG/:̪9rގk[Qq?bGf\ rC}{Zk3Or.&Z3&35'@il! uY W&Mr` H(pkdOwIcNr4%q#l*XE#&cUilfl kO08:III8N;jyn7_?O&3PF0Wf:hiOfhuJp_.oi%KEu9^ KثŅ@,Y\Ƹ]>'ŏoG%h`;ѷ'rEVYX3/ W?s?wtC{^y2 ;< X4A#pHOvh8aDEkyU1[r>Ȱz \F,㓗>#;6x۸2\촿m_ᵙjћ:)QHF[{:'yCۼn%j {Z癇E)śFA1# ZG{S+?ӋpJO\G+@ er\rx~mp]ÞzOz+u꽴Ն~c㵆V`l~Ek*vZְfQ 5hn'FT,gmbVl>4 *,B!B!VTe5Ujpzca'@FI162p8<1MbV{'88~Z;v]v#&&;@1L~8Nr]wݠrѩS'nf> ʕy}saн7 {oa̍]kЪ״j)4 s/W}*oS@d`79яKfE5UEi~[=)3~~w9ۅJbԺyuμ `TՄZۄ(ۊZ5eg|Esj&&V\!B!N]dD%T8+ЈMFR\\|< Ѿq1222 ڵ"9z O9nǘ<1f+>gy|0F&OdBu}YΝGjj*V^/ƍv^_/搙Iǎ6y:w @EESLeٔҵkW u` պokq)A. tM0U q500YlRQB!B!B Ղ/EER!⬘Mf||Z-gFC(HXXcժUu^tt mڴ󸘘f3s̡K.lڪٳ|>5bŊL:3zh yf̘1M||Cf̘Θ1c>}::tdڵtڕٳgnO>̚uMc޺F4]G7~SV ͙4k&B!B!CS^^NyyThLn3yylhh(SN%$$_{۷g}͛ХKWM{q-.\Đ!Cu82&'ppwLPP0:tt́)\.z)=oBq8*`!Y o0tLhWh814iu =}[luhn(QS7a2$+B!B!/p39 {hPq;׳*D*J \ M3Y͋_n&!.x *BAA~wSIR>SOHH[xyyѾ}f3m۶<Φ{b1}4f;3DE)!Pi/P$$*)-v6 `f,32[}9{>s~L6zcYaaaT\˲25:uag}~[5jY[nsス6lM4С:tXGt;~dSyka,{tlsðaX`lv cADDDDDDDDD p=HvVoč^=KsW^Z>Ms^KرYeܟn,rf<(lu1T'yoS@\9o___fu͛S uF~ٻw/$$$0ct*⋃ݻ7ݻwx<ܳ} :aÆt: :3Ɨ^Lhh(ԗnQŽ{\װa9pؔ)j HѴz{+f竎[5;n,.>4"""""""""Rhu0i<}sM&]wԉ3r%6@DDDDDDDDDDD2cmc_ٓ؃]=#;>N{*A:ZR߮"Z)rAfÆ@}Q`IZ#Z=wVO]4mV3WirQ&ʕf;ODDDDDD.,cd&m7Waɧ9֏y/۞kޭh [b XSX,DFR2M޽sͳ=0x|,Z:4.q>Y-esz}ysYqu?s<۹z /Zv,>9w`q$t QpTxuig _DD䜱6Ø:7ANt7/d`^lӝᇬ/ӳ$&GL#B#.5IF-J?; sY<ڲj'=g8uHT޿uPB'PDD +s)h.:DžcHaWSy$ z-d*1ֱ~qu}?;_ nH\Hآ:d4]/8dSYʩ|gvK\3n,pdn^Yoң"""rdl+L^GG:mJk[QݼHH{i`>ڂ(_*{]kK|zv1F=^B^=J突 1 Sض;l҄>j>1U6~b_1uu=RXIް[L7b=ѧycՉo8i m `{nb7|+.X)cc^g̜U$F2亰&1e6;fsb' Ք8QfڿP # IDAT”ҏG0ⳅ?*|^x.QJ$t8n?:d› Bca8DDDDDDDD.(wcߗׇ̪(4"YYƗw)m4UrtƗ3iy&;/7gM˰98=zUq٬x4={"d|4M}啧pfoeGӞq"R=}=|mh<~\כ?7x!./iYĆRǶկwKnM'G.6g][Ryb 9I""} ~%A~n:jNGJf_̿1d-y>Ղp͠xY`Z#?"[Y:ȰʟGuzjЮ@k|1r9w""""""""cD<6Q)bfzNlʜ,爩F_7$/xEM䵼l({4i jF|yߣw˼Vqwa4ibҸ~5$P?|'<4?戥v5'[Qzf陵;g W}:֦Rf־'ZJ%LMUѶIćncᝳQӋә/103TffXBHQ0p4ȵ}u 7=aSbQ@3M9Ac"y*A56ZG'oGSn8֒-(w""""""""""YUBk6PwIYKr`cIa KOB+'Yac<ȄzT|IC3Y}iX̆4|m8 kTBBհ.3_i\,ss#:a#mm_ac .gL3b(/>QPufNDDDDDDDDDP `K̎)dn[@˿ZG6{H.CLJ獳|ݯ){On3g7)ښ>fT`>b2;t:G身 xdӼJk@'gO`JNZ;I/ٌ6S3[qbw@u>'MʄszK:b_R2F JY\#IٓlS©n]Q"CAdz}Ԙb͚5_$NwmS {1ؗrKJJZj8NlӻVoB0l6;͆fߎay FkÆav߬uʖU D䢶kv5hwZE2=ڷspz,o.ł"I[}WȰazq[V("""""""""""" %E}[(5-l>`pa8Q L/9ݟLdLC'Ȧ؊csKl {,UpEDDDDDDDDDDD.AZȲط% !XXv_bcsa8baeN[͡NE{8cqWD:^ ÓM4rǷX|.Y(,RTڻrasa|0A%0f+}Y = 5l۰)q&$ᡸž+(R>݌nEs/m?ʷwcuzdǎh^DDDDDDDDDD2H7u9%}oed@1kvE߾شi\V6.VXfzvR+!2\20Ďײc>Xp`Zv\{=X |CHߵ.\e;r7D/ ǫ?{}sDe(ķD]?ځ `fް;hI(j5ˆ$u6?ܚ_z֒ B51((UerKb,)^_[~zS9 b !靼,T'ؐ4j҄&GTt"^^6߉qRt-n۽]Zw]Q@"+%)uo=~~2ޘħ=}}툯'߆"ز,̙pO?7x#;w0 }r$NsbLby=6? /؜_ v'a2lX˓CqOQ,pguea؝ؿ+ I6>-׏4D^nyC{Y~`o3iBIO:ʑfkia?_4JJ{&/;SΟ泻`>~/kODTY3 Se~<>=/ʤ9exS'pO_KO7s.^~Gn|]PA'3ÑTaذm^ sHr;v&=}?5k7^?![vmxu8x6#GsNzy7Za4oޜs;&}fZNǎ7ciٲ%2|XǵtݼԪUZ$}z@ -ebczř^-/㼔0>80g^Wkm8qf^[i7v+W50 - ֫GGBz(]dKB_ANӖ'șlN|}}k/F *Byty#/׏{։$x1S"S?jE͝.1DxРA̚O=!!!(ēOf˫y0``c&999's 8ŋ]0# ̙3)Y2nK_ 8^޼1}y:8ٴVvbyða/Us7kIfނN):*U%־ŋ`bO){=Kt\qG)W~yΪe6piY5#6v\=jSw+Wؗ/_}hD2 fßYFDDDDDD9bgOx]REwf~.rӖ1}r7ld9H]&:zEuչH"~z*Tbǎx< a`%*W3s淄co^$##ѣЯ_?z @bb"k֬eĈL0<14iwRKIٳ'N 0uT-ZLbb";ÇѱcG FuXhmڴѧGu|yֽFyOW| ѽŪ?-`ވ@:؝G83ZϹʻXS~{)֑GzvwV3l\[s%шg߸Z#3x"e ܱguѻ_͋z HDFdy+V5 a, r&sv2e -V7=6 t9 riO \E!huqG Tg隗x_:_El)L<墫wHO6 nݺ0[nG8qD G7I& 4i?36$}4 p8HLLd&˖-Çtp8 33ЧGs\ڵ[9)(Dߓ>Uz B*fe<3;m@dkyaԳt <~?WVǰaϿ̲붹[]O)a8nxJͯrSFv n_>z{ yGduR WV*(UCfeh?^x~nz. 8:Ҕj(ǭ?n{ngգM>(Nx0'Y)\KIڰ=x,ODOg̙BܻgLB'׶yL|%Z5t.gxr&1K|IъvKeN;ur؛`.l^|Bg z&ϯ})(^;Gk4Gd #FmO2YշT:_*O/)Og'ٓ,aDkv8xR1̻gL§,JvN}H>T`tSJ%?uBŸ t䯈|n` <[@9d)a1tXGڔ^NP.*sxVr,~~~,Xhxbj֬8"tTtB׮]hWe<t|4=X%ßaY%gɗşW:>砼92S+q{#UXu}{/(7apg珩<];N} I`hk?Wa9,M%48vR#w`cx{moui,IbZt*^UJ]ʏ[j̱zh:?bZ,siӢ*Ar)ZuGfrQLթ̲A 7P1b" ubA)= J 88޽{XU2yd֮]СCx~[5jY[nX.OGZ[ֽǵ5Oynfy,7ͼy;L6/OVeAh~7?xF~砼9|By۪MwtذXWyj4'!T7؈HhOj10-2صq iע ~fF ~=akՐMSw;qmr{U껙&q=4U:[?L&ob,K*FT37ےy8>T\\./ѣg} NZZ5kOR^Y-OW^!((o-ܢďu6ϰߦ 3i~ZP{}.׽~Kamf!;1+s)[ T* ueο(VǺc򮏾.y\aY]l!i]o-ڄbKW""""""ri:0vz2k^9kLcqwfٌWE."cՅ*[p7 /uԉN iׯ/=iy΢E Ov 7o '? e|||߿Q'dY'nֽ/9I`}t=\Nn!q[y[s>%q41Qoo؍ Ǟ}$o܏wZXDDDDDDDD"U/\,v'kh-~nn7ibrVl)e IDATH8y+`Æݖ7\ތ`J8,vCύ`qh]G㋁#d~a4휈,\æHp\X{ t~Җ4MrssnZ,գU!=8ؙܓo>8PD.M>别X^p9HaG1BÉ'$> '">>lRDDDDDDDD.<%EBdd)A.(4q\ݫȅJDD$NS `)+%B `K""""""""""""""%EDDDg$y,~3K˘C!Ed8V7=6 0@Ժ;[TRd ά4v,I49`_iv;ޡE.bJ9cemǏ1۞Bjhӣw7 W"r"~/%iVdfP23g߃ =U**,""""""$UyWw;<KQJxQ"%ED䜰&1IoZeUv(""'΂_[F 15Ѫt>,JvN}H>T`"""""""79gxX]71c?{| K"EeYyq: 0 v"G+ƼJzU?m:+feɄ79c&ҥQ?,C3F ylޝA-5ѵV06r?#n'"&o&v}Yw?z\Ŧ%?$y'{3siѕx%%+u)?nAtT0SK͆(Fxx1lK Zw5-iT/fw2zJ\7m >}~Gvܭ_K~ma=Y{sGh*&uǼ3~ X|"Eemg鎺`m6CJR2h"XYvrv\DzjЮ@k|1r9$/Mbwt/^o~>a&sp{Kup~{{a+?HŷTUMʪuҜSel#"=m`Md7]29%08t """"""r~̛_͏Y% eaZI*ty!Ot&徉I[w6{w86`Ζ7?$s%{2ѕ$Ԣ#Or4zvyE0˲pKK ,m^aJyamf!;1+[x`>KyKEl@9r>^m/_M`~+hڠvQXaҤ%:xyK2r}FVQO-+D.ۡwEk'Sb% -q;f 7<#Pd:\g{h};EG|47G6g XrL~ 27y#˿xls&ykO<78؎'6/duS&0S@ `"bYar{⊨JlNM4-"#J) , 1ӛ9/w,ac/Oἷp-ޫ~ٝILekmFՒiS/UڠhGOs>%q41Qoo؍[:%g) cL?JTiFkodžRؽ;3r2H%ԑǼU* 5aRR~n Tp֢Ɛ :RH=U;E<)R g}ן=nvڴi&#cgubؘEJ9e /O9/+7lEB3^a3ʟiF8PkRvG$1Rㅈ\4pSa#mʝ/xnCkJ<_ %;3{DDDDDDD$+u3c"a0 }-ތ;_S>#y_ =7;'\osHZ^PvN|+Pޞ9(ZOY˘ J-QQ_O9*ph2V{ ЁrsrO# '"<@',<0B WKZ69X]|5_,ߒ,gx0'(Mi;wƲ,Ə׶+,ax^(_<͛7#&&n{PjU):RRRp=~QqPR%Ic %&2m@H0l6Z˹aGiu];ҴJmpۦ9=ˠ^+_|kjE¦Ar:>XxnP^f+\<\T|NlF2m:{ :"3̴dKӸD9HZj+Uy?85D;AJh$Śi-FHê-2i܄c\u\{ldn/wŔ?Z:A,K9.$viuw^J@nn.Oq|t^G* M5?Cpxx<*r[2x\uM/F]aLgӴ ?j;b/Bck#~hɒ/NţcܟM`+VVE)+r1ci۵e.g?´Itm/~Tn҂m/dLg0W4*EATmPV3ou4ο1 8 şI4B\,c[w'pMGt(%홤o]1qAn:ጹk/;VqտΙŖ'7~avX?}"Ѻs̒²L'ezL4r0c/wVܹ3Pp 0y,o$ )Sv㎞,X_]BRz 6cDzgj֬c0ˈ#صkeʔPn]W>C=ͩԫW3ӤI;wФI:tɓXj5AAAӇGyhL믿'LeΝԩS!C^%>>4IfϞ͎;U&:+zG0axn&ƏaCٺuaaa 8[oUB7ڵ;7XšCXn%7Ͽv83Yl;Xt>5xa[[~̮3T;17g=6DI VNnF߄alv -g-a=7l`y AktO=zӚ_gQlYBD.joZv5EpΝqgL{Y5J/w EEԱz bccbI49U=, twz;e,6rfCXgk䯈=є(Q7cׯ{ 115k2bH&LOff&;UuФIRR62|pzAL:EHzz:cǎeataÆQv-ZDV\2͛7LҴlي+WrWAjՎa߾}XEӦM]> Eܑ. `x^Fhh(',Qr V ^9|RAٺ0(,"""""gã*8f鐄@ JDjo JT ({)JWz5{# E =9|y,Z(6'N\ZjѠA.:t@^LTn2ejyW^G?GujϬVzJDDDDDDDDDDl7{l߾xʔ) av229tfͫl, 3+CӲ,l6˖-=Zj%we:q<7_|9svۺ"r37nxɯ-[<.9<¢QݨӉ_HQ*֑qWPv Vk?E ,X&!gALA\y(V>GDDDDDDDDDDD&vSg3tPiӦ . N͚5p,[(%q:glFhh([njm;vЩSsg7a\.bccϻQF4jԈ1qD|Kqe˖e/[I._Ώ{=pVghzb.^f1l!.>m/gȠ֭HYWjoOU l """""""""""7*RrX4/&==;vGزe3&M@ӛ#GGdd$gfӦMvfvlHHݺu7,j֬AllŊ%**-[2zh*V"**ݻ\bL!!!<3A:IJJĉ:wv8P}ի,BCC ǢEP<l޼ybtUBdM ;NJ.my97ǯs2J8fws3(V^ސU9'6SqZ>x,vaR$[odz'#"""""""""""r3i!!!,X-ZMxx8 6dʔDDDn2jh;FTT>WvrC_%88)S0tPBCC6ux晧aĈƒ'OWNXXe_#$$> rfa *}K.0d`3f vˋʕ+>Zr"eǽyk ӪI9rUE;Xٹ7<ڃƽeis{I'R~!^~%[rv}ɒ%?9YZWu2vryǍxbϟFӽ[:T'ڰv{n" T'\cV'H+:tБ-7Dze{1k\?MetWFLfi4@ʮef}eQg<SLf{~?ojv\f{DDDDDDDDDDDDn*\lY6oޜm0"ף|60([,v3摎nhB;~_nhx=;c Kǂ' 8`X_ѣf?A-|{~EXY >ҥ30|C9~AI :TGD$#co?F|/OPf o9_|9r 8NW^Wkͨ)uӆy!G>p:~u.eЛ]hvWjoGѿ%GDDDDDDDDDDDfP\_.'\KD~{=QTL >p_PLu%t8~jM}LA"יk? _24b7#Ʋ} `%'˽4YڞnDDDDDDDDDDDDnj \G,bӦM}v8v/QgLBThГE#ޏ Aɼr5 gtM`a+WWBˑ i9UwLa5 G䂬d}U"""""""""""7E32%KT5`XO\וּvNͽ<5ٽi/PS`),rsI{ͻЩZwJ cEI3}‚[c"""""""""""@DDELq Ϙ0t<%E(b%&9hOQ46)+"ů6fS ucQF-*ըOߟSs"alWRl$6&[D'g/ّ5Ru|>=BLLV44ONCؗxN&=,6.ԭ:O86+6aT>)yi}­`/>`#cIL|lxADDDDDDDDDDD/Z$lco,3 0v:WtbqQfщzYچ`l>IF"gRb؀UIѕNaVjGʋū=Tz 1>?>`e?Ē{&~ԻyN+]!E(YOV௃+""""""""""Er10p:(AqW~;\.e IDAT,W@\HH_? |uo;JtqcЫ)c f"1! 0lޝ5'R=BZ8eO "g7X:t""""""""""rCGEYr53Dp`L @u>.H ;FR3V>U43׳Uk*Uv<[`~.=;A 'lkRYI.z!yrABB!_{ """"""""""rP \9+Tqe7ҸtlӓqLJ$#>v(֩yŬ\9ftRܻh$/+&gCyn]5s&╫=oN!23g*XqƭK*i 1%td}_cY,"""""""""" -""WJ!9\>C/{NDžu(D .*><Ru1i*}~Nݚ_L"t܏OcLwQ0 Sc4nc@A4y9V>=_8)ϝQs8gh:wi2T2tK?+Gm ,!۲0-|o,4ͬ=$FIQiܹ[#?e&lܸUCDDիWS\9N'6ۥ YyB66-_;ada|o0 2cM.a`e?PP!u\8@Z/i -"""""""""""""rPXDDDDDDDDDDDDD9E# 2M bb3DD119z^^^<ȿA`+?u8:Z`Bppaa/k`~A(,"""""""""""""rPXDDDDDDDDDDDDDÇ+4,`(~Ͽr2s^DDDDDDDDDDDD䆕3l^Y!}9 w&),""""""""""""rsx+/Ċa=yL;ȓ(e֧e)ҙ """""""""""""׽\>M32x6&,{HS #zVǙH,1Czϣ7y^&DZ_!p*DUjmhFמd[㉊|&!Y5}&0.Y6*!HgŨ. Ђ?FX{_`יjEܼtz?}f.cqChDm:<]~}{AVq(.GP8UZn5 '&q EȒ8,a%;{10u{ΠnoW/,A3DDDDDDDDDDDDn27hP[9@6O/ @b~ƤH=:\Oow2޸6`-Xeusxw?[g}<_Hi}}vؔM`\%}v "zͧG`mV+_@(yt*l7A/0F}$({#Lv|U:Bp_DDDDDDDDDDDDn7f+TR&=;a]>EU*&yW>l9܆_e26Ҫa_ ] .D 6V2^>*ᧇ\DDDDDDDDDD䦐8hȒKO-<V1ŗ8hnTleٷiaTTT Qb(۷FpujNcݪ-dX[;S9~-kֲH!j,tε(D#xu928d*/?]>ew>ʗ`Q5&s=x9,Guɝȉ}kݷ%cX[FB|9W3e_g%f/ѽC ֭Kh| tod /X:4""""""""""rUŊe׮]|\,Ӣ^[zʕ|7X-n65F"ce$0Svq>&&熂ߵ1a8dgiJRrUpPPa~= ,:9fmWQl?'=@oucQF-*ըOߟSvɦ"""""""""""ש\r!&O/>ի'\ukt'sb&ϛ˯{r410KExfkӍ&,v4Yec+|;J`Ҝc4v[zʚ=Drux|`(^X'%h\II8̢teð' O\}?' >Ar|g?F\`44O~bWx쳤Nģ@ LfNYF~xKGsozro/ՉOgܜeÇp" g MCof$|Cf$`:W"a'v/p tGã;a$t|\yRc `@z!ʑ7O Wxp=Ep030 ;ƖE5RXnv'ډ(_C[JwKajT 'iGW;]5?k[ը_vNR]{1kr6kԣ--]iYF*rг+% R!""""""""""rqFһWϓeJ F }JA9J.CM:}QڏG3*΃U?qm==%CD˾k"!/?_,ߧ:p|j\v<v]߬ H3}Q#irKTkN;͆aذwZ+'/z3} n(9̍ٳ-a4z*=U1a&xQ|;KSZ3Vnb6"J`'Z$p֦S9g8G1""""""""""rrֆ w^+S7ߺC; D &}k(X:B2V0C;>F K&WuO?NNQt.z<ի`F̾ccJ8wuǻdvzmhd(vv傄3B9j\PrbI @xyykuY8q&g[cr1εǡST$w޳ /,l}b1w?(v$~_&93ȟ/B۱L`z ? VޏS[ ׌u/ܙE9u0U 6wwNwYL oBLVOzjWUڷ.?8_{"#q:s*uyoiTNarA<>Z“3,o|0Em@KGv%xe ѣ"""""""""rKIL$4, wnsl6vnqqrɝ\7f)?+~ĦّRɥٟ^d#/Lx,;e8 eX8Hf͖̉[1=9kyEQ?"T#VJ`퇽_27+pݧ9=mDϋ˟| 4-kALA\y(V>狧P-'/<屙٭ܗ;RT>})Q;﯌;b#e"CO/Ae}mub;" rpKE_mFҷ闟ƽygi4f.;gmwyμlz=SfC阸hv-/ȓ/1}gqz#OR+^ɧ_b|t|}OW;}H ﹗Z)Lj/ߜEufh'-DZr8g謹_DDDDDDDDDD$,/e1M<Ͽ, ˅$G"TZr*7 4¤VC `sa8pxc؝6/;1 ;a23ܩS)NNd<v'cNOMKRXJ D!uX{>׃k4=1B/`AG~K%)x%GgpK2z0iS H[pxfFy3^dPV,X+5\``âM+i;ߍ@Qf4f}@lAǏWS8=w؆ixxQWL06\xL߲y婆42捏R(Ł_!]y6f˃a 8PUf[ W.~0~Mh+y\}+ѱ6GQJ1SQ&=Vl *J n-x+C&7c%F~C>x2?u\.t</2H=7ZDqc, #6^ra7Z=sʲ9}qBruş[xhC:N=p^;~lΣ?9F%\:~?4sQX$9M tcS13R13ay2La``\E´<`z233L7+iŸ=~=/T XGw063(V^ސUo@Akҷ7ķf*Z4_Uyxd N5pАvsݏ~u]|}Ө|w!/y^ߡy | B `#Z:_`YZxՔ6Q%UVc͸+#ҢIfw6QVƎ[}S]ܺD+:0qh_#j n)F>|;f%ryfn՜c}>xOړ! ?ÞY/+,+gq0pv4H.`d>b^.l~ypȇ#0|y`pn`,0,,²X6lF;##GO7`MRa5)<XE^oٚc3A敬I %yĽe3ם|VQ"iJ^`{َcf޽u-'%]\\.|K'"ba,:IxhBݭnRS/WZe6(@lsl{1:wiGj݈BDDDDDDDD$' O $%U,=ޯm$m?LrWCO/ig3lx<[ƻ/|(X$20l`Z`Xg_r 'gp sٙ e$JFR˖ŬUD4yMҾ_M3}ӨT%{;p>seGjT;.Ƣ[Q"DZnW8'ٺH0|q\p ٵ!783Re)_ ]׋\&(U68Sފǰ"45 /{+VKɌ\!uoo9??gSy F gr`OCJHI8gˬײa0u4/N#?AoeGR*]Uǧ ~c:(ԩSٸq#{&$$0j(zyr>}{n :"8k'׼L` <l#_.6ĔuX*jE;!ñ|wѱBz֟;65_IN84=-yΫ [W#ZW7t݈%2IJHpfy7M#gZ@9I3G?͓5z23f_ g-LQaشCvH?23Ul9`>],;(t ~ 4sMk8p )))L22;3>r2|ɥ< #YXxr6$º IDATB1|/Mvqx668_Iթդ;1jJ?*;f@EvN|{= 1Rչ|ӿ مf \^sz1#U?iyiB7ޥ{?x&=LΗ2|3ig֚ԬPdF6p93xOjمa+ ȜWDDDDDDDDDr p)Yqϩar{^ \${"O;B{WR~9wӍh?v"LJ,2ӳؖU[})T$ ws_H"E8|M6PP&M//Bddd˯Wɥ23tfaOf9aL#em0jUJ0yw 94t d4txqM /, ߤtz M>g~&Sw} m̮ T!z}x?^B 5ιir/oԅ#"""""""""Gٲ^nnqt 7xcn,3'7g7xefi" oFeCn!)X:AD;|:ADDDDDDcFŮt)ߎp[=*q_=+ͮCv!K6|n駦"̾@lqşR]sǬQmizr>8}z3<\~G"KChyidOo0n8|}}Bz2Wұ^X6'޸\H/3y=>;8uE>)@@Rѓ3d3G8|ƸѢ]$>Yp աZooeyѰQ9"B\8C(M]޿7ySMGjVwD/GQ5'%FRA)I宏3>_~,Ӽ Gxyy1c Fɚ5k߿Mykhv f25/3fVd ##r|CL\J9I&nvm_ONKA^ڇO{W!+#AL7ߍcm#gi0ṉ`.Fq.3 hV<=?a_ooO ֻn[Lyϼ{FV,\쳓 s/7s#dY|sQ0;xϻn߾}DEExhҡOѱgee&f,̓' `ffibbYxrXh͛嚦Izz:?{ͺ dXZ'<,s{/,]F=' 6^slܸUCDDիWS\9N%FVG@Pl6;͆f׎am026 YIeO0t0DDDDDDβ(P:B_} A!!׼Ok35(W% `\Ʋ,2222v=9>-O~8mrDrҲ,3oY^pYCK[f:ivp""""""""""""B Ͳrhl 2Ԭ!gf3aY|/Whֿ!Y`lؼya p`s`8HqceJ_KX3 Yas\"g]Gv@|ztbtUJrb"sM3-D춙SxIHN`¹h`\0 tIW&9CWpzZZ~gszo/[GL{7%<暌w`]S"ʖyqkn  $r%z;zb\U}"!Nt\228g7c Ǵgٲ,l6eʖ}ߗK`vQ:A$$$v͆a`)KHH^^^ U`s8|'t֝R}X?1~F"D#ڂxJ'Xm[k0`H[ u-dwHKI&Z'GعqHfm""""""""\rJ yBC)QI edkuv?^^^l6;Fjjj>> "ap8 vXDp I F㠬 N:?ƒ"I%&hO/5 T]:>jb#ů-3l Z{F,2bQgyucQe,;M\ٴtNJW!,,B'DVJy;`_`,^4|n2o fߗyjKǑ(5>u V2[ip*^yùQꖬ-2m>d/Bgr_Tdr~߹R`A‹SB9?_H-vh᛿$5nO/4M.ƨEjчJ;. 11kRW5F\&1q"zѷ'ȭ:a $NEV'PKửԸSٳb};49fpblUkErx8ϩC#""""""""ED*H'6.G`u*Nax7۾mڸ f`/b{(}&/u, Q%]tj鮼)kXCk_8PC,g2?G[ύܸBQક6_R?m5q`$i}~W{F 6X+:UQP2"oY2gIXͭU5.7#(0o1qXYw"EN)&kl=؀E9uŷ//!Aۯ]FDDDDDDDD;XDD@\64mKwѸL^.T`a qƛ:Ɠ5y<1?v1+)[%n|tLDDDDDDDD亓k֭;cYb1M zAziԨÇ趖eHdd"HH_? |uo;JtqcЫ)c f"1! 0\zw>h@/KH B 8BhXz==\j74(ZzϘ;fЧ>~_ol,I1qsd^:ُebH7acģ`#I7y0(ۤ-Nğ{-_t.k?˖[ys޽K.˲,zIɺmmo8r@Zٻs.\DCYZR+[Msȕ9ҴԴ4+_iWS33w*P.s~i;r?,7@-~B!B!B*Mzg+#8>>-Z1L8ɬYظq#۶mё˿#BTHllT4M#++˗/ނeMn9ř. c2{66hɩ`Ўyܒw`ud= /{6^]ya\,gv[޽NR|9QA ĘKdR-z]CYRC} /eu}˚Eꋋ!ódűd 88-\zeΞ=ŕNFeu,`7jKkcJ!>Ñ1~$qN':T3n=^Mp#+6>4^z]._D\\,sbbbQM6`1=KGꫯk1͛fjÙ' ++ӧr7\pMJhh(K9sgΜ͍ѿ9o3OO/q[id">W:eF5B 66RHIɋ#.}~ ~li4X9Ye|;Z>NO3YTAXq#3n>lڴAYgh8I#㙺tLlikkYO/W2l& {j5nw0⎣becrf߯ޅb0bcB:-g}ve?oǿL`e_[7@㰦Dn:C> ^4_}]$щzhXݥ\\\h4J,B!B!*k#tutt]CϹkZuM3k4m  $33GG'fGG"ˤ3rHbb z]~eӫWolmmo?|0?&4is~ѣ %&&vL@@ <?@ڵ*5n۶-ڵf+dggWo:VbڴԨQ?΁{.i߾Fk{VZ2mutTBCCBۻw/7 U5*4(JeEEɹ( 9QMNJ]qB!ns=^5kJE!s^u,*/2`@9x ';̳>CXX< 'OjFFF&G1n8iݺ5&%%192?c2O'$$xbf͚I^9s&!ؠ:څ,GB!B!B!XrsѢE\x|t]'((躇[gܩQ&;w&>>˿ۻ5=z+W~Op yWW7\]d2KXX;v䡇W^)\B!B!B!Bˆ3qDT^wwum7̞( +T.]QU/ +++V^͛;cڷرc>|B!B!B!B|xpwwم^z EQ4DiҤI//F?EښĢqر +o_4hРȷC>;w߯_frZ!B!B!Bqw=W\IHH̛7/+Wdɒ[RWWW~iMlق$&00@;7o븹֒xYfѤIAAA>Uf`SO=a0Xкu+ҮpӧOu6ȑ#8;;_PB!B!B!B;S /^ȰayɁ6CU<ҥ_w1q\\\Xx1'N͍ɓ'ĉ^!n[{o~rsZw?7ueqg4 DDDR!BqݻFcGgij@QUTU ኢ(\Grx!B!(Fxլ)!bΝ?{֩4]HG-0\=KBRWleEx !B!B!8uFgO;C~(u3HYrH0t]˯kӤtjg5W.-_ȷlh5\1vujd a!*1]RB<*NJj2g/Dc6g)!B!B!Bܥ4ދ O6  33gdx ,BTrf]Q)PۗQhB!B!BqR0ߋk9ݐٮtBEs1vGDv[697&^&L`yF[E`!*94e`ceK:~:Gu B!B!.c[9=]a,rM׃^m5=2rw`{Ҽ -Q`,95 z|/t:;80+SY 4cei-d'H7*U Dkyu|6x')2qcCe!B!B!wE;ok"pуƜsĚ7\ wFgfa6Ly-;oE3cew8OӯeiRwL@G[s9 Hx{ #CkLXE w# _ nyWPϾ<2ۊ JaCy1L8Bhom9p:SCn <<6w}͜ڈ l׎1zG IH`!M 'OFr2ODQ ."mM9| jzs!Q4R5C.1>+6N".'²s"oߩ$bOY?̣ e$۵`ݢJ4yq7CZb}ƫKN<#fw㕎8(ױ D"I'ٿb[ЬvfxދlY_N&{m_\ o=ͅ4aT2 IDAT9&n*Z\_m׺c'dggcee%+*[y`B L|AocPBVM#~#ug`Z9 ES~ߑf^Z H4x[ؓ 7t;+B!BT"J}4giEqAABeת~ 5oС+pmoY5c{ҷ'[j9P؄iK'-E5 Q%;ȵ3džr< ^uW*BH$! W ~4RB[h6b>Z@m:H&CEqI,DP'.maAŇŚf޿7~_>zHw3vc5-7.RvP-mpG`nuQ7BT/ ׿%~pjV"%@u>sW$Ma0YGfO_2tZzMØtށܲ=W[cƥ3qǼ4s4D~?l&ʾ1ajCbRIH\ y! 9w.]i[9lTfQLNq<ՙFN>V%9XNFv=$B!B!n2%k??Cjrl eq!H5٬'gD-xNr Z:ECdeEPg8BY:DN'11=\RE$ i:dxh-_Z_b^W/!U@ЊpQfsꊧgRdbz>0K Eц7!h}=$l 7CB1l[<p<ΌKDұţa8 ߭Բcsz+|?5'>ah.-s3kƵu}/Bʑ_Xr(,;z4kߑVdzc7ۗzMVNoWxh34)?XJ.WX;SF ,jףa?߶PZ '_GcK,a2/ݏB!BqG4b $P̸uڇ9/UlcHꗌݟ\yi8#[.+9g?dpݑEH3a^H|.=`6@-s{f.]LUkľ4, դEVm}/ócc+u{t2C3iΙ'9j'rFN Q^*5lnO*S^ˊ'fSq'o0 zFy$ג|0|5MHg_is^zR˪.k%L$rx! S?n/ԗl}Sga7hj (6^'nr>t<3bJg{Rr"SF5˜~1m#uM[۲Z%iaV C=*uqۨN8*iD9M%|ҥ>mBjQMI? BFGr NϦ( Go)8[7amW7wou^N̋?K7S]=XW8ی{ 8H8s?2sR\Z) 3bh&oοV1m&>O333:۹ 6{oӆ1fU `D9ÅjgY=t~>ɳQ!2t\^kRYQ4o93z1b,^lsIkŎ~Xg%V=j[\qv@}~3FW f-xmԥ ,iם@;#eO8OV=yydq Z ihog1'_di֧ S?޺qp[]u'o)}9YϷVe׶TGd2R;UBlѓ-}{s|[оpnBP ufۖգsNa;pi]TS&:p'`΁/vuҶ?;#ٴR~۠"vŇl+GrAɌ`߿*M^nپm&?oYǒ4IPc-<~>4 ԴM7r2NnbWWk ;g\o( V:/'ggT;u#ʣS ɱW窕3N|[zu rP !B!c$T9b1~vI͈d‘\Xxpwx͆ 1{lːMh9}F)# elϐ'ɪ2nܿ o"5%{gԌl&ܟ$V'튂_b/|!$ (U:%Z2NPY7mdʠ~5N:6}cdDqqB/sp4)6: qVF[V83cH¹=h~IvU|5CTtg&:m',q iS]|A1_77+$dWؓg,e8O43AZ65$몳뗗ܖZd;H?!9ː鷕0~#p~7GX;8dߧ.lcw{I?.0oF@Ž he[ZE.fH޸ ~eڵυ._ B!B!u}hpy??J/_qȑ'D=2{F4hP%i3`luZ|3];tUS; `ğpagFE ?#44tjWC|8c-oco}t :ѱi6f- ?f.<ǽlݾn(@OP1۔\>rRl *.z-yE+V8G^͛:sf|ʙƝk/E[>WREGous{GX`aJfɯMĚ\QAQ^y>,.cz !B!BȪ՞5OPА+?dtBT{6nn,Gv/v- fd~oS#l0\:uS*R5vRBT~u٘¢*Wod,i!s&nQͱеRչJĢEuP jVϝJ.(Zeg)rA;4G|QsNe,_byXO)!B!BrKT4$BG·1 ر#-̧pG_!$}NjsXs:&cm$U!-6#f.۶ܪaNlh`A~o.{&[q3>9R>lm )! ;* i?qOÉ.eɟ;.`ߜф+N/}jisz}vx}>ӟf24I./ӥ{vjux L߷kAe,HWGg|;vi;8{ꕾ^2}#k@&gY2-ue_$Dz*%ik ,hc}=[laٲl-Ξ>t՚& ܰύX guhF'auT)^ z=+3~.{iFnA8Y7R]/zl[/Xז88)uë|Iu)c=9B!B!Dգt78TO=g8]rz :Oh]4-xvo 5*[N}Njj*kkBF0rܸi=ZSZ5%~Bj3dX6R8nӪ, Ȃ5t 3so"M-Rf-TxwتI׏ט6tXspX|cۙм%+mДoI^/`U:V}2F?1j * /8(I:+{`KQ\tGoṖϲǷ=ؗU2`=B[ٌ{4ws,{m rnG,мz3rc G;yloKU<>kӆМ$jx5s Mxߦ< v⥯?bBj;^ w ;IΡog]?>"B!B!BTXTzںb=aw2aK9,`a\iL&""" !B!B!*/gWW!DRu$ Q[SWK ݗλRB!B!B!( b*2 RiB!B!B!wJz[RK\ʚ޽{ѧOy],K%!B!B!wJ/_)ek*m:-͍^zQ&%jԨ! XLLTc. BqQE*A!&ׯ_ׯe_v4ŋе=RUnSUUi !B$B!iD!:95M{پ}{[YYqȿ *%UUquqnI:EQps`0H !Be[{Rc!B;KmI r4ū*ӧ@RR ,d޽̛ UUqrr#AT&`4G Zdgg'XZZJCM!.PWB!h)\,4RfS17_4$! VDDDЯ_?Ǝ bxL8Cү_??^涛4iBvh׮5kƆpڵkG֭6mpuuzXE㋓3~L8ٜd䌟_}/_^d|-uqsss.9D prr?;99agg'=B!pUW)[KKKKW1m5wrNQ~L{"RB_ڵ+kģ>Zd]~{ٴiO'E~y=>̚5lrתU}#[leĉ4i҄={5j]C\\j* 0dfΜٳg7ժU#O(  lB!DroW]ZB!DդߖSnܠn*W_L;Tz_Z5k2ux R!By={u'")9zCO@@@YEϞ=tӮ]8niuOOO:w #O!BqÕ${ݜB!b7EBarmJ\u߯xIƽ~mQxשäq2q7o8hN iXӜ<'?\˶opOӧ<//my<3h|U*EIrr₦ide0E#--gggE!99ku̞=cǎceeEJJ -[ ,,;Cѯ_?`oooTUO!B!n߫SBŬK y 'BQ(5 ;H{5p(\-WV,!p鎟/Ǐ˿m{<~u0 IDATn1ƿgPk4d"Or"3=b!LyGTtT 2ƿӬM&_˸aŘfSܑ>J"V%rƎĠ.uU^RX_Շ77-6Fmל,q`!BQ%ڌ)mD%k"Ap ,!p/GEG3nҤSŋV4x6\. )Z,Vn`G@\ 9]54f"DċlVuֱWFm&sU\N.]qwwߟ#Gмye;Fƍ8{jպ)iР3m CO:֭ ++#G쌪J/7!Ku&IZ+'%dǰmn83.;C׸o&H&- 8tO٣f4/ɹ$N޴ [K";j5AFqyc^۴2C*ArFuܿ?!Vj)\ QU@sb߫C_]fżo.( B!D~+\Di0pLJq۬_+/^d1@~{,hX7c0cwӋ]Y2=7؛ߤs*|U54phh3UVL:/s߼[:tR`f̘̙3?>FOO||rz 1{lN:%!!!̟? f<#1R ʙ3=˗/U% Hg'o0 zFy$ג|0|5MM {sEj1 [cF~7 Mz'׉۵> L`Oq#~då틘:o< aDS˲d'ǘwMț#/A KG'=˗R>(n]ʰ%:z~ v\֯zqC!2*| ,s{KuեO hgL+΃V)]\\h4J,*kp5~ vu7,t^p,5,BQ5KNy9mE V(''G&=*::u<~Md9a_|^W"-%sbBYͷ,_M+w iѢ%;wE~ʵNXX+BJ"wiV^tvR0<Ɖ+؃筡bV9eFmj*I$&k[ѲM'e>r% 0 {RrEU&U`Ն~4|-p)fs/ٚ_4s3HJyK  ~ . _W%B!̔n(E9K(fJA "n~fB"9z" QU_D0t'J*ׯ޹B!nc!|6 6Yt#׮rlኊ# {o0`@ìcE5J4Əesi/r s6%kZR$#NvX9Yfm`$s=;y*`0W7o^P\K+BQZWI qK/ynހυb ?g.=+bܘ1so͎-Q:/{?@f<@BP_d+SLJ!( ?ޝSe`>~XFn1Z˗,͸In^527Ð6N_Ɠ؀nR.;4 y҈^UB!5'w3h|nO212 g'ƌX"j آ/uoYh=]}q1q>?ҩ2)?a3<8s13ǖ ˭#V44omfc$od"vCZV_+Md;gg줥,Ɓ\o~|[tWC42RߧA 9'B!rZl܅XL`m[ EUHpIۼsyo 7}?>Ed`;HN)#6/Y T$BqX0e>n>H0c>i&>Kj42]dx.+S1[ZZ-]eՓ3ۡ.=rxbӅnW] { =T#QG.9{%%IK6ff3YYYRB!୑R˃e fm lr{ kŇ(IK[!gϝw%!1G9 { OťӽY,~JP=a7X tMrLu4͌ihLJb}Zh-]ӯpAN>Mz> !?͓7ݜm*sJ,BTrO]Ohiʖzu8u/ !BTFU?&[w 66P22ҹr%:ҠA#+B!niƨ+z Jas#bcS!n M*-Dt]D:(xqy!hr܁ɟ7ﲞ??pL&f^7K>o}GQR!CE:)"JS4)+ !B +!!H 13#B{mJ5ݮ̞Wj*J ]+B!(T-0c6[n@@@QQ\;Tgp/N^ *H!!! "߮7r~nj޵B!ĽI6% ;d뇁bBqHXЦ8 zۮUǼbb`kk++BQ[y3t0КB!h#]{@BqHX2 wࠠX,7l0Q#`Zo8߽Wg#bرx[ ݞЗ}Wu^!Bq] soNW&M{WmD!Bwm(Qlӡ7'v嗬.=Bb'`!ʀs(ԨQOXϳ~lv } gӳZާ3LB!}.Pй֮}B!(֧ƕ޿9`-gK_!5.%*DI4 ( O9D<==ɓ_Y/a^7>獢R<˯H9$el0B! 48OK/!BK%i۔hCTSB5ZFB;X˗wl]C@ԪUX93>MϰdxC`?s>ٿA^B!iJoߜ}4 1B!Ѭ53WQVnܖ@B8eMuM'N9sjp6OW׻Q,/a~/eo2j]shl5v=ѳ* ?WF8w##^~6_*1K:Q> YLfbf^hGdshg_a[L%shK hK:CLGj9U#v^Y2>C˪ٹyn'O?BB!˶bߴkhM|4!(NheKjƩ_ʀIi]#v,=Z[陞<4f1c+\5{?1l^,G*F&rI翉l3=?X̤fdo<9O+ZO @K,>ˁ"B½~=bb2ϿwΞ;hJJtlזG^gԻc9q4 B=38 +ۺdW,z9 2k4hjhʕ޿\{]ނJ7`!(iL>yMf͚7ͯPQDEyoSSTx\RNWp$RMjKG~cc4Т[ol%ZL/gޝ_Ǝ<9}$ nڹ>'2uoL 7+(b4wtx;M fm=;NX:v ,8EXzmj| \kzR}Ғ,c_qRfmFKB!7t>^;16. p.$$5̩g8u m‡cĩEn !S9v ?o=ș +xVAn ,QUl \|ߊ/\yg9}9t)W&:b-)/C6KM^]:UJu>rQ̩S .JT6={*r_QRF̶X\ %%)$!-e@u) ӤnD.LV'jJf_8Zǻ&wXpT-بItghzCy-#d_yh!mӏ!ຓ~e-˹aAv>XzѨdpS-dEcT>3sGn:B@\anZ,>1C.W*^JXU#G2o;ď)_!}vOޞĺ\dbsJ%2pXքZN5vy$~&ȳ؄}[s7INz<-ӖBo!޼e哴 'C.Prela2eШA/C_#(8X>&!Dl&dһel29qhb9̂O0 ^*.$űO*ϴ>sG9M#*e7X|%K~(ѱ >Qcz2DR2|Wx86 5}.b8cŹb=|n(t !' X6%9R(fd_BYNpC, _9=)%E_.t 5'OiܨqQ&ݾP"##JWƑ͚(1bAgRϯ䚢\Aެ 2sA:kmnN䤻vB7>I Hپj hYm.qLv:XC v Em ~.:U|{ +0s0V/|4.:Kqw5fw@-ٗьnsLC~ƶ:%m gx쇃mP :9@GX~NhвB>yxЇz,:禾TIƁ#y 1`hC}3sXпF5;r* lqO%>F IDAT fG绌Q0iHQ.ѕ^ćcYN(dp|hXqtt:`E17_o\ `vbX~=O xgP Rt)B$""Lgsf >wo@QB̛=ژrߖ^KOO޾Dk)u]s!v%]tlI7_=LA'{8nWܿ^ߏeW;e(c 3EvphێW5aW^#+ĒQH8@wˋHLIxi?S5d ]'=m1!jIFMcD8|>FWbVyh\Fyi^N~-;zcӶJyA:xY\قuʢY3=n%(7K?CF7f3{U4RL֡ϴ5KXrU֭_σgr2^ÿm~w77>l^N/6Oaf{hHZnێ-4mڠV6moVb\Sӎ{ \wPeJfeijKc~G=lK)[^={ (cc1R|:{<Օ}y/+Exļ|0>hQC./|yPF{zqͺSC[|~Ƴc2x߲;°&9džW,u>?kعǯe'1GsKѝjM`ґ4Lis4|=ǧaӟt扆x`9`~<> _f1E~S;GӮ4 n[/t76t2{\vuoQλJZ{ S dSM=Y$Q ڦ3MOҤ'h19ۘTzbzε/B!p$jtZo I\ro/F:~t~'m;ĩ-xl)SRpIN4tny7Ýi?xկ=yQ7$%`rIr4=L M;0Nd8Vp:Ɖ [k~* wQt~j.. ҠiѼTkQJI66Wǎ3((%ZQ6 ddWŠzڿ* ]%dEv_4{/Cce]MױOE]C7 'UU1We*[9t& Z0aծ<)1lQN) q')kp(bN9f]dի:LNUB!aժq ?AM,@P+SQiȁC1 T*F?3hx릌m\]_oE-?zb`4f_|xq]UE:?݊J.5x믦 jk$δx|/8J- =vމc[#p!g!!|6g!Bh PUO|C>Xx iE/pq&:yHB~|BwC]*?d/,$7yf/n%SEuGr$/?_D#MMZ>[TÀs?,d?/~n#i'WE?OXPeH{bwj%sh$wDrXB1wpic٥֤zEw Y7SWȼFF w]3u?gSUP{X5&"EcW6UZLۣqH,թ||o-gBU*=@5HJ^j]>Ȑ~P:4dBqFҹǎ`Uԩ]g'<˓SRTwB\GՆMT,7צn׆Xsx;7:S-f-=4t^-P m*ϴL¥3Kumc(tv'Ͽ9dPSr ߰2}:I0'D`プj6q,qnQ`!%Պ#N6 GqB*ZUg4 `oْo bȋyRE)o]t2#Ƽ Ew-*=~ π97idǗFlmmMH3ȴjXUu@*;g d̎Lj,I!D16(nc]:=Ȇp&(1c>4j#GYj5#S|B^t;g5>jEAQ@"ĪbU54EOV2Jy|c[q1תtd4:39E6M96S0cK 0*u#5;s@ qg{f%b28SN;FߗZz0`Y˟WC܊JAB! E)̃LhK`͜}]Z5kQU|B|*vcX;~yщdF=PQmj`&l;Ř:{<*Ӹc/^+ۀ*߯"46 hH>a,^31f*4mtEq۳RڸI$򯅯фEqY|\KVyQؕMORnoYKpq]r'#y9|A*(҉nYމiL .ά^QbmM ޾_hdYuph}'":  9wuF~;}r"T -IɤԦEִ퍗'+Po'.#L[|  V[HaYw\mkoG'DR9 BwΣ!όh3# JEAi5 ]>boi|Cak,V}!MCh38e 臁n Gy|a1OH߳]BR0%i^h<~ݰ?6mzyCtI<$(3R㥱xyٔoA-{zѢ8Z+`osT}b ,b7-h+nxwYӆצᵻEEGse<==i޴ m[SRy=նɶg?JV*5idffJE(cawu;tl8:ӵS[:~~̈p>h5s{|4V`gq{ssg\;Cc( QB!&b!..SO>ј$l"--xB!z_-6/^wJdx|u0mtˢYet;#}fyW~V't;5DL{ֲ^*(G(B!nGUU ,"aa$99UUWBSi۔ď5̘ѝ9.Flmmck4CxbB7LD+1nIw=ӿPIkX o*~{z voZ2=j:G`4_=N2OG_ޚΜ~~i|TLYB)׵LY2k%ZFQtwZĞT.e#th6 8y ujm.r8Mee(.Pe"Nhذ!Ge͚5DFFVǎWތ5j2uT.]FJJ <3g~BrrҨJncǎ|󰵵L8~TF#>>>nݚw}ի4~saߏMޓݻQV\ɣvŒGF`"^2gg{bETb< vvvFY,BAv_JZY-|bj)>h&ki.Biq'RwZjԊD'|2ڶmK 9rqE3!!3og4eܹL8___ƍCX:3_,,X0^N͍hܸ1&L 33s̞=ݟb=8::Yg} 6{nڷog~'ر\z~x)Q9EEG˯xw'pI8::bggWl=B!FUUf=DSլa%U2WB!m#Dwb{zT f߱hҧOu!\ {S ...9r%K( C !00^9s+ϿXk yi333YhaЬY%mmx9x M6I۠Aׯɓ'i۶-!O#ӓ-Ю];\yAP^<:w'?CpFF+ݻwʬYYt)QQQg„ tpd2Q^}7x 4k֌~m2f;YA'xUVr1\\\6lof3ffͷӨQ#MZCfqOz9c݃TU̙\|̌[1̈́^d6')P!(AVR"RQw>+3-K/z.B!m!ĽXڽ-{ qA8ҰHш{ΗƍjԨ .$&&& ШQ[sذa >tӴ!!!DEEiۦMk .*UUx15kPreYH=Xv-SLźc9&O…3a‡ԩSUVѻ3l-miر@.\@||<.>? &вe Zn N~`ڴTPO>A>ϳo%|vDEG/Ž)( ))ɴn;;[:d6)O!(qrLș/%#Bi!=30s7RqV՛kٿtvc(FGG󚝝DDD׭[nԫW-[0c|)ׯ/qqqlwk=קaÆ$$$hbFСCi߾=}kfyU5jTK/a0xС|>ܹ x.]ʄ SOѢEsf͚ELL ;w,+nW߬\j.]~BqhW{#bd ?]/Bi!TO:d\\\qqqeȐ!9rLtNصk'wbÆٓ~yҤIcV^i$$$ao^ϙ3AӶmۜ Zn͑#GXgЂ+Jzzz:ILL (,iii0<=Y a2,viC/^\jB!Dadd\Oy B!m!6JƵh}-͚537|e˖1ydzM>} ѬY3o>h2| L =z?EQd`!HcW(<&u_ nǕZ׈^#9ĉӱ,ؓJ*|C+"2w%(?6˳[]`~n4̧h|"뒊q_4a)b]תUӀLNDƫl{3z5˚bl<ß6qs,'3s/rdK(<.#(xaJWgw0 PYV)!B!V >[Bq2ITc9R93wIR"n!4\YIw5+; mVد/C|$´2H6nl#$WtSqtS;l $k&6lȆbA>ۘL\!Еs筎 ) =GO\biBrwo* B!BÊV Hi !}JKL`fF/=G8ՓSsl+g1OVbߕ1#q8'cҫ쒊]Gq[['z6w؄ l*=8vc2|dX8x0u=^%!~4IMdtBV"#\<3qJ v;ٗkHѪ˄&6 7sX*Ǭ4gq+ўfԮ4։l0\\.Dq˴}هї; +EOywғR͆CVAyqA`X:U k*AĄTgJ!!B[RBQ8|ÀodBFHKwg\#N dg?k.L|!RLDnT֙nv,3_Y5slKLηg[HKVQ5vaM'9;V@8e틮0oG43'#,V.7YMZ$OS;|< N46ݎ+`.Z;U{b7XqWsяܞʂF2=ZKx"?\tbH8Ȇ#슶^w+N lgӯ\:oB!BCܹhL;h*:M^ѡCXi_5vuXudzkT*pJEAQT@n1X!DY,< ڬ"3ZyP(fM!3Ҭh|i(rgFzm ìr`"B;+-$&ih0E_7-ѹ` &Tyw&iBB Glb؝(%BB5aS Mbx8|hI'Z$[*tJ~k& akΛ}yr%A]i`C^})+B!BQEY9hh6V͊YU8*:4Eaof&iZ #Αƕ.q'Ht(hxB) \ȨndrT|`5޵qX CE*Dr X/grꆿD2aѰ _yիdD5<3:#+ȌҵS*:l!Ӝ5/Yșdomusa֑>mi\QOztՂ%{ kMʵ0F:r/3g-Xԛ|>=EF [#v쎹:!DqYŜbY\wt:п3.sT&Xx!Bq:v녳[9t:=NNEQ~Wt(Y+ޫr_YVۻ'l]-SQVͤ qvu:>jCVTW{A;ql4w|u̶NOB wDv+6Įxzg9OE+HMz-!=[f~&ِN%[fgg>3;[~qc00 z:l4;P:˱+[o5˳izRT`P2m~tb5f[~r#g rv׀]+BOOǶw-!ƝoݹJR!EDDDDDDDDDP~2/dzAIN(>>#/Ol"^Cra'(r{.vX `<ߝ91 bnDwd2lb;қv*bEthrYS][ݛ{askYYʞ8>w+mi;9~gs 2фv[,r0m~Z)On6}3w|3F/98Ԛlv/O;+Φ="FNAhi*_mS:6}>f/M3]zWoNkطfDu{Z$ lmLǤm\U VlN~z`/w`6vҚL~~|9'j > v,&`:سm93&0T&cjz5% DN5浕f\Ȁ/Ŵ61_$z<::_ Hqk />Oy#iw}/z!z$@m CDuVŐvs>t:qɸ<07'~2/~uή y;]s^F<-ͨ'yEruf-w1tpw"?%m;اVCul*0(9tP,rXZ/1 y3"#a{ؾ{.[?{0ӗCdD{7ΧI'""""""""""1LvO9LB]iRm;JL8` 8'|GW6HI頖&h& f-st vpɟ76X7|RoN'qۘ;|,eڛ_3p,z,—p<a<NĊsؓ9V<8Gsh0.1shaۦ|k0ß߿]n` |y./l—|Ϥf400cMHw` luB"fiS!C^laXv"rH8`` G8"o(1 P"+}رGX: db&af{lt:C9Ί :߁e6IjՄCfEGldwiŽ{ۛK{̙[,)5{s >ဗ%ٟ{ "[Xq`X4n<~-0֒k&֘Ǘ=[ke\[V!UT[,`gsZYգUW_ %ph<`( C!T]GM"3}ݾ+w NhDΦ"1#KMLǴdwV=rl#idR׬W< 4n>`X<1&ػ -8q#6 {HNS wvaGwo%cNwsGxJƍ0wҍtցo )ֽ;o]Ƽ ɱᄰy̌l@gx_OX;07|3flvWG_J w* K2w};cӫg>b׊Y.46dφm$Os'H`H""uac%"""""r`OX$*6VI̙ D!22EvӼk7>4 sѴa5r2v<Z[1}7b ?:s$7B{c~ؙL'ӱ)iu1 e5yP5t˳_}Oj }'%6x͎ -Σ(=cW1wm-͋ױ!ex IDAT7-ά\vcT]ؠ7t@(:4ibgض]r¶ .4V3~"z/ޯ _}Q4jK\['uyt.>&>vAcvM6Jn"f*@TgƩ]^ څqofѤޤI48eL:roKBGSy5Nrb>8ʟf\D("> On'f?m oaW-sRD02`8sz9o0.7F(6†E1JEd0JGAt\b*Fةr]6Q^UZlKBai:[ICڈa2Dd{ͷٱzwzٶ .Kt~#{ZL5 WlF.ِnT-O/vwEi~"'txVh٬wz46-Q k\aqŽJ6GEΞ}w[§;upslbec'0wmvT2+ K̛|N~ /qv.?` sC\e[m ].B߰~eS""kX9/x"1M4ȅnIka狇T1 L 2Ap. *a> ?ٔf[Vh `[e/>. """"!1ibf#ǬMs~ie3'֬kVFʥm$v%y2m{e0lYmc:=$S@fQtNUF:)KԌxN'o~MӾZY՗"_0QMl }ebca`i'`С0BmaIoxk(l S_n?;NLvt:p:]nk[oؗ_46(DmezCo7D}~,IR,x>Y^%4w<-Ԍ pDFjm0 )#km۳']Wȳ9 Eyu]Ж~=b؀ $x`+_^/Wqe 45ք(6ĶmLӉalxx[5oX 80/%?+>SDDDDDpm76 \,%e kqQI~14IOAzӰ9Fl2͂5Y4zJiD"ԍu6ѯ_=6ݶ>w N~QڥX05X6ageG+nae&ǡFi7R:&$m?k7>e3_2%ݝ&fq'fa5v5 [V2o@\_u,"""""RYf2tXtKw"q ɍ[Ӹ1dVmdEFn$?`PkNNkW&-c:)3>ׇ[;PMwVoٻͰV-~.AԄmmax 'Ndg0_~uglBM6>YڵpZW06ׇ""""""R>8vp€N,Yŋv:5$9%{_Yn kE[xhպMR\Qt}ub[^K"GL%pe خ 6 l }UU.8  ʼnrXޟEDj(4olg [/su}HϿ7M`9L#4%oKZ1[U6 EDDDDDꠎmΝK c5: pQ6 h|"rJ@ [B⦿&6vx_lۥAo(Ԁi##6 {oEEe,5cU"r v6JC_; u\kr/vc. J.00l!Fa\'hP `pӀƌbxKK9Dۙs\V v.ja䇧ְyGDQN.|n仗楯)/&[WKg/nN=OnfLx?ʽɭ1۸gZ3LG h(;fpL08 xˆF(j+""""""""RE\v 84mqІaTtxP -}%"RCv5(Tn3mҨa$w}׈xg3'xh aͼi9Gk`|'uoo9kăjd/Į"vf9&&1 =@ǎ #[جe __k\`͡Ͱֿ+j\ `K) pEl#,.Ӡ7|.\Wj+"R%݉Af@k⁡0][3 oOehM3x*:=0u]ofv][_u,"""""""")+h#7f7t6?@vEDZv h<ҝi~ߙxҵ,>-n͓t&jJVH},> ;7GVE`j0Ms`rVgPPG=co!0W"Rs셡 r=۔Ƿ|[~lX\g""""""""RsΚM_ P \QHl6ԝG[?J#!]r]r11_ǿ f2YmDcX=^"˄^h<_l\[z>WDDDDDDDDDpVC%Ap%z]FWx,"rl/ $OW}[8d>+7Zeq{hڎ֞5s=NU[2!"rrʶ.Cۂթip`s"U[Rq\Wt<6B2]B\gj+""""""""թpU!0 EApuoIlFx`ceHG~ `wx:hCjb־LySGt6Nv߹q5tnpkr靈nr\(:.w"TrG:2ܼ&vV62rZ\?x(l͹-v|wxrg>vvVy7WҦ&Ǵ'boy38Aȟl ි EDDDDDDD* æ'KnZIP,""5͑ l|x2'fkVfT2M۟]όF&M۞㙄2.O. nSuň甿ċ~F;pj7ix'5;x*xa h!ZD2`e1_[}HMQ7וmWs֮t ""m"/'?||ѱIY>-WU!x_)?Ne[Ve- ˲BdI""""rNr! )41M3qibzI X 2`GaC;fN"Y3m46_OCjugM&*>Wš-m*m,""5;H#z XlTyQ{ppf⊻.5]-v5`X*"r߰W_9PfmPgLU?N:ӡ:ȑS~횽{/ >dK.~{ ~+1Y#~}jx,Mm=l?S%ûv3y]Ԭ&OQ<;ܗTJltI'uw,V+rl3 r%\  `g3y}~aPrv}m3}9%; 0H_Uǿok-;düd7_c7,_}]GyuU9EMj""""mهusッg3{|@ ؓU%x\ imvXTW""""R车1b֭*\@ 3\:v3c<4u9wdu%?8-w=Nj3.Χ]ɭu'=LyY&ȶ=AJ 6.`wyU31}'Na{)(NA `w27xŬcћ3[Eaؙ~eY̩IU_?/aKmεqUTJrψI8FW\*Tf&Ԧ&5(Y=6y&Z]xîvqnfL~:}=5{DdpvkScmwSѼze+7i?m?~kB'ϣ 1#]thA$@z:knv_ƨZ.I?ב7Q.kYQUY.0πnV1fYmml]ўiy}}R &&%x1N$aE}+ݥ/b*?'wq|v1 ɰnY&?N\РmZeE((-B]ۊvƒʭE0Nn77%O1{/c4H2^HlǑ´][Xu֯>$4vgO͜^b+Fㅗo?_Wu6~zzc1}&j""""R)cITܪةͺ/tuOVl&пM胱AdZGztnNtNɼ~a],1t؉v-#=3!M6-yL;74a-N39׿e7ҳ[%jU|3h=~Ka2C k~JkUrWW ]թqAr։^]ޭ>O>yw&˕7HR_g_ўˏIlB Küоћzk1q ΣGַg` _X |݄;0.M\shDҎ,jqFɞ-lwUV`E$6QIJ .1~漗̝6M]_)ݧM}Vnm><8 dvRkEoɚ,).mA}tO}}pW=rWi~3~f*&йCc 6·QX>Us[L1Ƕ0Xy/EX\m}lЈfr,=.3x執82ʰ$e 5\go>v-נg?{-cPMTQWGV%ЛZ.jVЯcrt$O>Fwfyt}+fE;,t9EliI6mph˂Ql;1)N6d< P4q{(, dyh9Q;rIz/o3+rNXwx6fmQp*YNGc[sV9Џ6ߧԡ>HݡXDDDDDDiֆO_bvwߐA?Uqt²WS}%z-?'oO7^9`&1x\D&bU˵f~45ѩf\5ӁlմziRY_.ۧ>Z,bU4Irl8͸*I㣉ڔ}l66[+^0p]閩zyw㡯1;%3jD||rcq9Tzmӹ<>>ߐ{y8Fn9+S8$""Rs>OEp\*QJӼWnϭ kvMbĈqu0Ǐeeе87 lhtb=|uS[o߶١opGIoF#(~-q3bmږnrxܐGmt}>ZO$rtxhff?V`X~m QCjiQ4̏x'ݼ:L/3Ȩ|f u}hS!n"3KsyӇpR4D1;g)W"''úeJƵ5T/qhLs̖Q4-m t ,ABؘ.z'V$7d\vo^6ˈ(.Y;yl+d7Y|8,kAcDŎŅgrOLfݏ0qcԳW93xY{C @-瞛GyXbЦQtgg_ʶl|fbh頊tQtA7!*3`ѱo2<^.W,tZԁ;kg%0x^tmପNd{Զng?/wyi҉+;ET~-W$] ƾߌpħr5<>nX^5OśgjDa`4ql0& .6hL׾_Cqa]|"d.iֆb g IDAT"xcGVќ V ֫i>L1Gcwԏϳ&.&bЮ_CDd<0#@^b'{2;LF V5ݰ5^)ͻs鿮ehkw;Uvw.""""uqzO'"RGٶmYfg2glBч .#anrx_CBlmll nXm/[V۲,+7@Nn~ *>ko+[ոdމC.$6!t`&i:0 #x00  x#4V}g6sԲHkLMDMӾZG-ED3{}~ZL->ƽⲉR,""""""""""r(fg÷az3ޛhgaKbM +?NEh"N4yiYcF1k襥Uv*>{]psy7>^ɖ}^ My-~7kS_}WX̪Yx]) {?$ɱȁR,""'.'|mfaF%Ӵ .hd򷻙xq/cN sn4KE|r:ScQ;`3N:VDD&۶-L ""{E%b&} mm+xmclYl²9YeG*\HlB 0ML u`Faa/&A2_??QfN"Y3m46_OCju,"""""""""""""rP,"""""""""""""rP,"""""""""""""rP,"""""""""""""rP,"""""""""""""rP,"""""""""""""rp""dž["""""""""rm{, +,l0 Lt8p88].N'axj{P,"rhܸ """"""""'p:8].<.Ái%AmؖE  PXP@ txp*jq EDDDDDDDDDDD(Ka~>*[Ep`:HlROaA T[նS,"""""""""""RXyyyؖEdt4.eB8ǃ률B1VUm""""""""""""u|Ѫj""""""""""""G<>15D+t:ڪmXDDDDMvNYتqb0 {\0#SXPڪmXDD|,kD<iؘ?0D+f1zU[V=i `9x>Y`"IhМ}.G=ѻeqャl"ZDS6Č~zSՏѯDO{Y5w6Vnb(Ѧ{?ze2 ғ ,5~v_jK[CDDDDDDV @^~>0Mbbcp!]j{`! ;kNcϦ|1}=M~FZ%yqE8E[ʊ!SNn˟Ϟm(4YǪD6MʋQ,//H#8"<򈍋SmU[(XDD3)D@> l?Yߠ|U[RtxƜifƄgxikܪCoz[ZG7ywdQdƑ.qPB׿)=+eIN;nv1֞T? 9yU5;=ão텸8ϙԂXFl3w_6Mb7xjrloV^{_!G.6=M#-&⋈Q[TmYx""erGFRzqݪja㈌"S Y,EDDDDDD:rQu5j!`9d&?  ^0\$McFfAL`rVgPՑFl CE?s_dޜ;ia% Yzx" ij;/@5w 9{ʓ\egsRD-*RS%OPk^pY%ǣmI>2O5EEx<oVkml߱?K^t4?Txh0 o&sڶᱫITJ<{~;Kw&wxgyvxkT|I iu7fnwi[_hy'3*"m9*ض%*:r*>snv?|{O4DL~c= t-r x<yͷ-aПQQӶMiOAAԦֶ44T:t@IQ hɼy[J 61w."ZieMh,\O Hˠ;-R5y)1U,!"rrn%\{.KdBfRi2sX儢¢txbg4IQ^=b%^=K!)ZEDDDDDq:FzjݪUI~_~i\9rӺnzmXvj8.v\98$3WԼ̵߷/o2r=+c߷ϟXDD(#x)+_}89s0?`\:l@`w/L >Oƭj9'0N`٩{7~ScIbLvif8e.3'7˅Kސz9Z`wn?]xamѓfT(RlFh_BYv!=X@B֤Q[64ћ]s㷙[ܑ4Vf `8-g뼵,Xۈo?Dv:(""""""R6,cHٻۆBQgt@bBf^۷qCΝ|Ozz=7oْ\\≌o4M> 𣣖yW8p ]:︝P)b[RFF؈Hd6ep~VqPk+oɗD9U)+Bhx 2  &,:0MLv`FcȜ6L i 0 Zxbbe&4<dwZ6o~ <,?Юw~+zs\zINJ"<2H3/X+zՕ\1d0׬Od_O07INo݊?>v2D0HARިk'zO¶|EYa<$6l %%eYlܼmrqǝ0Mc؞l-]:w%̲7lȳ/::!%""""""R~X{1v4$-7a;u",4v1e,[3dlݽg cbhذ!+W*tن wᅰ&%b)58w@ML YHe<:u>;Z=]'yqal#]Z6l>>̟I:@>gxPd*j3>U$䯈j&e]w΀bֿU+;9W%2IØF?nU>f9KolO%˖pE젋/`ҥ&%~-tMM0/|NһL{{2ށ`X4wU:u d lg ΨuEi_o%x>Lc0Xccg^൉o[NDDDD$ۺo~oM~`Y?&cȥJ/˲pdoETb;cұ}{zՕoۖfĶt)"Rh,:Sw IX,=~޿7cc|&Zxd$S7n{I}$""""8N}>l:ُQ_O>Azۓ}%k6>U)bK.=W94^n=@Ν {cRf|?ÇWؖiTD3 "- ۶9'^2s&i )ci=C _fpR ?Qn\{Eo͐i$&pɌnk1dx ݟ3#oی2Vx; +VEϻs}pQ5Ԫz""""siN<{Ͼ+ f̐AӶM:kNJUJ|t\nwTc0u1RXD40 l =fP_MQD9@BZȚm܊}碞 ҈OTl7D3 O+?idZ1 O{ ,fDΧy<}>y5[$PR?Jz߽-E5wW""""@jJJi~7oYrlasmcɤ*w DD*~9 i%~|aWɜl_Ʋ|ؖeY>2  &,z8i |l_5 3pad~ړq]n,Ĕx'\z,fx<* svZvPuXDQ2BDDDDDDDD &5%%RS Rl[Ŷ2u){\n7iZf)-55sr>bVdJQ!!zO|> Ul[Ŷ SXDDDDDDDDDDD2BBHJJ¶SvmHPHTl[Ŷ,_NvvIJLĶ퓾۶INH?VUl6%EDDDDDDDDDDDʸ`N'I X'1f6I 8N[V-)CBp$zIt*m9RDDDDDDDDDDD| a$%&H@P OZZ驩Pl[ŶQXDDDDDDDDDDDq݄9'08U*xHMI4M""0MSUlrF `rpJ֬u IDATqQmٶ'=t sVۭ*m9H9rqdxR@VXDDD0hFv[)#wR8'!\Q`%EDDDDDDDDJ_ &~LY"]~∈L#Γ fu†);g:D"""""""""'R `d}D^7ߤo6uOAEDDD$N+$'kdeJI* Oo`%EDDDDDDDDJO swIVZ*震myپ=;dpD0F~۞J|}.(kcg'~ܽ~\<#߁9Hڹ ?9d>3ؿ@؆ǰ8'A6g+ {T\ + 8 !"""rJ;/ܬD5'!ͳHQ$[X7oQvwRa82ki f.a9 If % XDDDDDDDDoNg2׶9߬YM &X _SV.d%t >^V1m9$G͝m+o_IKśy;TBXDDDȿ׭aN28ǰa'Nw`3g Ofn#oO,""""""""r |Dm޲_\ʲHKII}~Dp&H?1ϱeNC1L&6YIJFvREDDDDDDDDJE՟ N{$pY||>222i 4Mjת7'0<>"=9դDD;Jyȵ\sOgojr"O?}zu#==]HgYe{vF{%09&QNoFb֣mʬ 3"""""""'BM%=5Ur'--X6)=o _ƓUgJ,##'7MӉ]:KV/ܽ~)=aUjs`f?HeAD7:%^ z=/vvhoZ5kVǛ,/ Y9s`rɪ2"""""rX{7q"˛ŨQ:Sʋ*5`Y,;׿~ l|ޠm۷o߿'߿>KNz#H%!cdzw9uxaup8Zqr&""R)G^<3cf=ΟKD, wKOHf%`ق|eìyߝ=^ )j#Eڜ}PDBJ7p/k2#oNd7ӳqU]nScxq[@V%z=i]\1v1~};c3nM JxcY<66A$yH\ {RI?̻7G{maL ?oQ%kְ ADv6O-JÏ㮤K#rۋ/&./DTnCy̖}Q \)00 RmmҪfQ47yu-"""C絏xtDgrn)]wSo)<'`v=m_bvj4Wvkkr).̞r}t<-OuJw7t5W 6h pLꌞm$^tJ1t]gT#|wvXĻ̜̯䲏X7=G_o34#:A~ul͋ok@v0>u"^bPk> Uyo|h3XOO3xS\D*)ihcΆ-^sk ƿYQ07p#x8=_&߸k~錓ޜ]g;?t+2ߣ)s{LJcPn8 @E}V?&3;e׫ NV Ή~ms|@f9b8uTn7ƌ#y*$'ݍu $00&Փ޽ 1ޏ&˓0$ؽrK~^VMy%ⶋD"R%?flXzc $:3bx Bk#&I7h{¨e?bTy>L S<ϟ;js':Ċo~V=x/lAxA7Ͽ7mgρx}lv&bCFp5!}o}iE+3>1 f8lp6w]&"#+kd&zHeSgmVlܳ=z 69s#~k/N`Xl>ԉ oFF六[ءs;3Csɳyc &U3aV-XEJX8,n`Dָ}ޕ|\.]USD*1ߦwpZpWsӶ {l} '>\Yg~ ]JؖjHd^kt<݂ Z^=EDlb|χ E]j>Ϟ\YXJ6:@2!Z7/>S:gřٓ 4qd_ѭ%.l!Vډe8iѵSNrVqNͪ=)&;Մ5}3gυ+ذ+4W84o|/q9H==K gVu?T &|ȳ̡_/}#~ӈgpg|q0^\q_=5$`921Ssg~_TҝΕڵŻnY,gs<#r@^ֽu=: $O9S > ޯWOmУ.$F4>3;F3'Ez_%OLf^WKF"Ccaya) f,@vp7{m2ap`yNDJL d1BU`)oJN|} ؛ r8-/'!\47[>!_wV 8qZdDt pܷ-Iݫ_;p9Zs3 xrv> g' y'6Kroq_wMw7`1. Nޭ7T&ֱ`|=ۼS5q\m-jT{oz󞺔kGڵ6$7^}7ϸ+:kZ٬Ւ{&^^ޔG2˭wp ".V6hIg/ FsK$-bSnrHP%_y6>j|"R%qiNf_(p(`GZ&>6o{m*9mt`9=^ti=YSaHjgƌjM~i`%m|Ż kJ-RSӰj4oZ̿u:6؞uq}}._Bm۷}>p4B.H[ ^͈qӘ3])eG3.F+RD3 X?k% usנC_zs NC= w.^8.In og|w.cuf|Oq޻"R8^ 㸠OPF:.tqzs)Y7oƠtۋ|W,t3oh]CCWKv&*ϕ"Ϩg_cXŌ܏ omz'eP>EEXDD*@!고|'SUDJUkg6Xq??[39\ȵ o;]䮕3F\nوslZ091+I4ك]޺ ^SeV]\c8]Ӹ/jYXИs.F\W}Fxkj8gn{%~5sf"*76S<)Em{f[Pw_u]vZ 2d;3sq\]l=,L?0?nxm6ʗʗYĺS[qK~-ކ#sxMzC?zuG etg8cƕ1]LzH%aTvsE,h+Sy<^I"έzGH~۪CG>#M5R\ͤ߮fR>MGMz;m%""q~QKG/ V`"me8 Y~P-œ+ i`%7?) lP !lWK{t4nIsK;*r9 }R7#1vF5aøap@.>35 a ô'qS 'v,\;x9""J,NKQDlXre5W\HRp'y@F^i$aȂX# y)dNvu6M1gF39xbɀ!Uhs; """"""""""m'Yl#$q? ڟE'z0gMzi <7\pxo5SF"_H f.8j0Btxj#sx}3̺ u^0G0UIs?Ս@MTfryNxngQ-{hFPEk԰!׬Α#>>FU0&v;wv֘`[ؖe[YeaY>o ۶H8|e g结 ~e| o?䘷nZzW\De6qG簿ޘ<WD*xFq'EF 1 -[vt4o>Uھ:Oxii:0 4L 0 0.TC}fc`հmVtk65\bA\bŊe p"""R.͚:t*Y=/5ad>6L02 0ȜIQ@ \% K3g%|zx""9Ru"-6"sZ*+"LH&M|wֿ]&BUKMg6E]6Qh,tnnEliL`)V,qO9EJ5"NLWDEDDDDDDDDDDDDDNRM ETDDDDDDDDDDDDD)BETw4Ā(""R)1+ADDDDDDDD0A ` B ` B ` B ` B ` B ` Y*mҹa0mj d߶iۺ9dF4ܙm)fXz%E(ˆttgcٶ#"Rd}6wEDDDDDDDDʫ2SUkx5g.I)pPFU:wkAC׺^lY,{2􊊔^&6Kb""eadtԩ"-y1MڭQa.IDDDDDDDD);zF5 /0?;F N6ʘc߻Iw'.'*ICc֣:nQ&miwZ8 ",iHwʙAJWlyHH)+"RL.IӚ?L[tPgqx6>QWDhݫbG2} }YLD*'۶AȶE3^w/M$"")K׎tur`2:y2mS $,$@ #e-/=pޑ7s> , :y?u\tAL*t玫7azTDfY<_o aSR J]} {-BDiS7N1!83&HiyADt}EGVeJymeO崀] ӏ|5"uisε3Ky|]rJ:<8 ?kca˪ibrƍӶqa6 xЈ`zӛt Vu؅yx}wsQtVqҾ^.(m0otS=A&)!?Vݵ33K; Fs/j= MŰy)d`аk}^o>} M\&a) i,ZyikH,h3FѿZMaMi_V9KS ]\&!Ư0u]:CTj N۪^byw7U<{Xzm/O`ހ브%nVxK]J)-Z{i#rM[8ZndN篈Hui*'; u*%rC#~z=̹ C/HyG^s[n1˺Bgx[uT*ќٌHIcq6N8 .L a`f`N ORuL~uUl jE:ridui`uܘlڞrcYz:* S'UjViRI#gPv8J(oM`hgv+g̺k o#eBۤkYyCI^EDM_⊱)O\F&QWQa,_]KkxV$umuԵDϠDV}3=W%@x\=u{={\S[$O)<5O_Fk's7{ї2r:/za@foҾ>i:\IsWq LׇrٻI+~7[=vX~O`Ԗ8aŸ w$i_;'?5iY 7l+_6zbұe l׋;p*sʢ>l8 ½61n I,.mțQ4q[xٽ!u4Rs;=g(jz'!JH7(] ""6 U@QQH B萐nG"F/>y<3{w3o;4`_VC;@ѯo5V v8zg#]dkFG{"ܜza4IEsAǟ3e2 1t |_M*S !ML72sbt;;;tΜɿOɄ;qf7h4{C ^o¤h;pv&Pm99!^IjM!jZ:RYhѪʥrjjOm*aL>* $&e/y0hei9yh4Bq/2ʝ5ql(n5c[~AnUƨܛv\Ϫ@gsgW~:;P #  5C##B7. NF܍fTM9嚆z!c6scXs|8MiQу'=u`f͡VԚa u=16|œЁgY z+8PخƚK zL,ޛKF{#.6 4@Um>=[L #]yُ(c0ۚa;:535h䷎i&5{5ڈ;uFo7Oe'^,Ak،?yLvTUeK9`9^wW=z=$\t*1weب.mG+`[f6Y"71ϯ _oRΗGY͛.(#2-]kw,fY\RuCGk:>?fFi$/ ѬnΧ,7#{guC !]1Ϳmlص\`WV5}gPX_5Zz4*eSeB{-N%њmRّmbְs@oG sVlI'N$/Y¾iZ^&_;ƠY~ݓ PgB9dIKMcA VEO32_uXt VX&9ƺ돳Ƣ}yX-;0*Lk툓qtW2[r‰ MӬ}/\[SRXmh&0j00GOWaN &=K oΥnHL w(=m6#h9ڐ yY2mr?`Q֫5|Q ϋMW+I--|'>Tq%Ӽ[fcj@ _t]Sj}JxeAe7StkD@ghMz֤a;┍~y*56fCMo"]ՀA~}yNHF~ pvP:w{Y{eֿBrߛ_~㭚<>tA^4vGY)in))*gxOViLT3aEӴ796OOԬEF홺5`6Nz|Tg {tS'5hﱩh-W%4{1wddžt,KEX<fglޑl:haEoG)򖱼+M)O.ޤm45q94K̕!M%fXV\6ႡZmjϳaa] %Uh\X:fğky^uXA#>:1(NT9>YCg 6^_>vŮ]ص}O|yƵgnЖ>ݽ9=w_P'XKYËʺEa>\9;b=&dRfi?;H![:8سu&*7}vojd恖Gf0b%'+ Ŋjh4©SӸ}{tqQ d[ =jPȡ N ՙ6~<] MpG06-;KeߓZ^Tؼ/i]N2:tBvJ6$75Z+8kzke]k-`tpgƅl :*f8kdD>t:CI.b(鱬/obF Y϶.FXC|ҳ!ݘ:Kc%u n=@&v祟BF%ӗMN s>`N[?ԄCWNҼ<޴Ĉ1LƔm9wa⛎$6>x)wVcu]Z~ux5iCq8ƌlLƀiYyeF;^]S6[jZ]gG1/ƞd Ûٝ;=ۺ[wrU>=2 L<&!ܿ9Nq)+X͞V:6#; #3hB`Xơ1or~!u@Ke =dDugeG|f9 :m"%5k֐Y^s;W͍EZߤFPpH h9WTZ^ {9jBvpt6MδJ@$-bK]WwN1[o9&8Έ!xu/GF~l8yӹ[}HKb#bJڕp;GzJTwCA&;ʄ9!Bʃc>}yekv4dK"nV~quh;7ޥoY#` NF8tx!~SҋnCmg1`ji$T#MHӽ^AqћQ<2sFeȈ`![V78;EѡT4#-ħBjZIsr4A臏Ͽci7ϵ}XkKZ{>/{%^O-EPl76JL$&t`KIcR3r70Xfapwl=Î%Gvb*-NfcOY]vۊO$FI1~\ IDATtb}VyW#4gk,ci>7^8keՖOhf@NSSf8ViB߸X3%Ns#Ћ[5:%_`Z8)?nֹ!|3ϯcK1ZQU+VtSsflN߼N([qҙz~F^YUWpƒC4> -^kclQ=hT1 OQMSW2cABnaڙ-qdF7ax8%/E/[cwr P,+Ю3U+ 7`+ pK:\NʈYX4eק_}qd#"_+.Kgl*BIW?}ykrݗ m^=s(NJ~x='|KVR7 LZ~Ϥ1 OaKpc9ʰ#5R y:!/9vM~Ȍ1T6;xl8,qx>Ko. oP.J5EqՇuF}=GdaC%2Mem1៰T դFihjVy_{<֒\`!w[QXܥSCS~Kz"MGBA>JuauRIL%P4F'{<|-J_ ۛ˶f`vc[I&[\~F uO:Tiiם'Cˉ?'vƞ%)Ê~([6dӦU8kRֵ_gu/buLkr -UAOK")uPB ྦh#;*Pv=qt/GA?]M0 ґ/(“ԩI+8s.QO.OIY1\>UyYb"m#]j͚/Qx3Lqݐg%/+t|F k寘i8v/9c/{ɮ:}&FIekV>cdICBO3OZI%moJZ_;yk}Yݷ`_]yt(&j1}cL/oJMSC˗aB{{y@1hhRn;MOwN(MVg gt Gޙ p5^.'Y.Uܛr9.2/_;kGR>N W4U뤥<[ӗwOCs=}2otY×1sHv\)`4hyPL Z0bzdY:c$ ]|8& /%/=fņBBHjOMc*xp!K 1QPi|>=*jѾZńYm 0Jd]O@+eVϹJAwZʖWg@1zwUc9hșM Tƕ]Y{ 'Xpӻ;97:JVF;)3f)u.Wԧ{ÞDǬci`]eB!B!Bvg婸7φ`Ʀņ5Ԍ,tvrҳ29`hΡN%_ҏ`2S;hB-KBڟ/!򕕌zґ%ht챷Btʵj6*=QW'Z V<ͷx.%y+Oƒ=CjS֗n ._#X%;@ bfuikN߳sljˀ`?EѪoYێo10R0suuUtTJ?؞эfu`APnjS<bWx h}(?g8lBYcٺ4QfRV18ŵ![xa@/d 1E1߽NxHA5Z2I1*Gwz=G9|ǶxJ̛9`yB[Yflذm7o.B!BqǹgS-%t4*spd5~iцJժbUIڨV&\q)m!n5-/<֊*Nݢz~OC.;n Ոaw?z'ɳ3BG3`bC +a:Ilrezj}JoT Q l9 n)4ݯkd;V:S՚kX0q.>#Пdڥ4ūv{?oҩmȗSB+Z3q)BPC7B?j5yNDў&O cyQG,;L`fl0늻rmu؇״7^cjOǮcԍNuFڗ5Jֽ(j .L+xu&)~15v;p2{IY<60SoOK1OSݩX9C] Y3{?liBl67۹QU+i(,\QB}YL)5ODW^g0nvx]G}|4/z{5MrB!B!Bb(T0tG+ii*Z// MU󟁩D*?M;kUݙ;FECFnYf$Y o;썸F4/Pї?'< NʫOYS:l Yu?Yc1RQtc5MEbid#'u˄B!B!B!vg}(5G1jx/]²e+?uijg|<\Ăq7 ̞94l+(I#Ǣ?ך, ֳ<07g򠅯 Sv6m<7S 4jV"hPIIIgl+!S44! qjy?2m֟ߤYsSJ=0_zY'Ay5(~B!B!B\í­o/ϰifk)ii Rs1=n(xh* 6`Xš Y /ooV?07 ,ږT%gi+\2 <յ"@D$g'/F9`/l8-$YJHG:7AQz⮦3bkv !B!B!7->jW&[ h<Tɉ,$e`aBM԰CNNN.v|7k1gΜqXyh;~*[K7slt;98O zˎ=)dCŤ#B!B!B!v49_)ۜgP/\ H%KMZff';⎟a(zP^;9(ϣ;)ҟ9^uc;1fU-Oe\ ^Ȧc)nYsٷnvl︶tWnv]t"!B!B!(ܹo(^^Ď/|lRjNZ, :EMCSQ_ ^kp"W3kR̙'2/XpKad𠅟QIL#8WZ+hVI*خLIF&]G瀇 ^NwآyE thlo&jX=쥥HlKnyH!B!B!p3`vs8t~}YGR0:EAQ@W0FtF=F!=:B#vfSx5\ӘSӋn~/>Wtw]7"T7As#^ 9 !B!B!@;!:j_"ig/ӡiCSt5 MSQ wr%&^]Ԩwg;zۛCy#|>BHqes/獂CsW@+Vγ}|MF]cybgq!0R9{Nޘ/yVq;w8ca%9NJz óRh[ 87;Z:'!5d Őq_}3CA }ž1cXϢĥBh Hu/[x{OG 0\6sBw_AO3rOm|)?>vu&qo3!`3aQ@z@KЁjJ:N <;~˦C4'9SVU"+&j6洅2Z;\VØVVKUe,ZFz i42/uAS]D+jke[(.vv#B!B!BAB DU]XW8|d)PN'?zwMٶTT^~ySAJZv Q=̃]JۉriXN!毻Xf}X-ӶTbXw/OI!-ઠdfpYƩԸ8¹ͳx 1'/ktmCWr|.z'-4 )qZZŵ M#'-=ұ )ƿ\(Bʌv*l>}>. UMWYwoH.txAUqTZhޤPzYS9)nLymΓ+#avM*/OXɹט³)%ו~r]QUB!B!BKXqs) 9mhf&;SvuRx<yPѣ_0˹ӣGCUt`Yʟ+a{/kv (U-ȥbĠW zf-c]iX$N/@E^r_'rG4JɵJ 9OG\jE rmh /h]Pn>WS 6z#~}e\c,F%`\J){Ye(5+M^_YFzNܾ:8z:8Ϥi\j]JNP!B!B{NB ٧- Lf[}jzQ~e7x5nEeܶH|՝#&\9;S̘͐?s $8c9IWŷk_U94B 6篻OIHRkU5Ĺ6IK]RcjᱎԪB`:ĭvnk9N&ӕ?CӮXWJşJx:dTbw#sx b%i US@-"jRZo@\e劼F|;>̣;u)ggW:B!B], IDAT!BSd܇Yo8&סkԂJb@;ji6CbAwmI2~f=Is[$5+M{w'6YhY^`-U,qGI+ԋ}bb{ej?i<244\w/)N切gHؽ]xQ\ӹl0OӶ7ƜOMn׽ޘ<]qRJrmZ΍/CDL)e};"~얕}v}=йJt/<3kcױ"JI灰2Yu1Y*wUKB!BH:,MI-iZ+::Dӷ.:Uqqr8̕Ę=:NNMo?5kyiiiD'U-c.T6oH{Jt'h iJt !B!BqB_ Ν$B!B!B!NB B!B!B!BF#r/DM!B!B!B'Bq3hY?=ط%8"!B!B!B!`!lQŵZs|K_513 /cX˂s?cj_!B!B!B w\%/vib)^UUTUj`<@S;S.s^g̏937<*~Son=/dzyѹ؟4~t}`INű|?}4 sGA5Ή}{H z!Wǔ{mLMF]ss8|ևi4,oLÈr(-zBzQPp`-a=׳.fi$B!B!B!AlrBs ??ݱpB]a&ߡzyw899]WY -._HHpeEmZhu)\J_qeq=4!bS,W~@KK&Msv(BF,#7<3ˏl-cKw DԌ ;i[4+ԡn0% _VFN:N <;~˦CQr(8L +rJ¦8؄d!j]%B!B!Bܩn{g9o/]wZlDTz`g's qcv2 ԫ\ Gޕu8Ҫ |FU1xӰEoPOXn3Qb8mkMb:(t>xRI(>ߗWOM§gҿ[hج LTYƩԐBDfzw{N"B!B!vk{nxiiil*R⦲lW=:eܼPѣח0\XޙMԮ_XB'0G5EGg:Z @j׳ #\ ѢݹsgeOOBgΜ.]h911seHII)\zp:{leBqq -_NhR ݺufPޢS/r4I)=kK:0I('6 g6^`o?ή8)4sټ7oiO*CYr*aH3ubԂL@ ?fc|07OqiNh>?Nss<ݟ6m#p(-lbsXU;A%յBٴ.&BфK*;I@B!B!Clw^GgՉ呑Add$^^>>>۷UV/=z%Kd*1`S9t(Rh~Ņ4ͯ&|C}+ :RE:Ewfڛ`='t毚Gbp z4@hy<^ 4zF9R2MfLp& 52&~ł:So8ײ)ޓ#0'ĞWh\E:B!B!SqGoNj[ΎV\I3 D׳tR֬YC=ҥ k`SNOj}n"m_VO捗V` ;_-,MGFR2E#| V;Ί|gݽ{aeKUQ!%Hl_cbFQI4-hBDEAAwl;3?:~=w9s9Lz+&XhxBKkcr3\3.玆GtaL$_!ą6@^-E!B!BYO<'h-}IQ&O̲e˘3g=pԣGV\%UU1ubLx&sؽbb x]ܰt9_^DKX>BcQpԦ˯%;k1 YTn'B!B!1 TwknݻwgPy$&$ѹsx ֛69CN$6mw}GMM &hy8z 1&ʝǟg6vlfZ<Ɏ#&np(VFvWډPK%,8_9:k9W1af B!B!米 avN,,hdblڴΤIh߾=[neŊ@aItO=6wa Q!.0sUm_!B!B!wgmR

6SLir8p1srCߠ@rJBSC7 B\p ~7_!B!B!. g-|>ZUU%=.Yj&n©Q0$,Ok0Ą%(B!B!B\`ڧzyp h}ϊ=Z-lv{xx)Cjjulf53k-IJJ:xIU708ހA@3dfB'  0pPT_J= !B!BqiB3JQaSqT B!B!B!vf]2x޸&_4)x 5evqx}Y!B!B!B!19kSTtOTo,B!B!B!糳n4]׹ƛxe !B!B!B!j9'4U媫~€Ok=i$'бc'n2+WWB!B!B!6-nfUF=nU**k1Az"^ ܻw47y^gMM-7p=>(555̚5o;w+E!B!B!B zhxxx1czMtu+ax<~;ҵk7f̘iA <+ԩS^lW_}>}ֆMӎشi? KѓΜ9={ѦM:'N"8 s3f|89998( *}!+1z̚5)!B!0jٸy']#B!vgwdxx8-ORRFk.l޼̃>@Νիw}7gAfeevZ~?;wjK^^>N=zbccIKKkx~͛}tFM^Xb& ʎ;deewkᱷzkɋ/3<-!B!m|?<7T'B!Blp%s$ Om-!AeM  q^90ܩSnDFFrz%^۴iCYY9tİaXt)deeƊ+Q~lz[okam$44_<op}g?ٙ^{-"""WB!b๧ywsU.<-,. raP %)_^689|l3k,$a2qpBdB!Bg=l2 ut՝ׁ9$lDDDPPP@N'66!{їsソ"))AѾ}&>n0v(rBf#**u tgYhwy=zУG&니 ##C^B!B4/:b"T|u۴7<2,wQcsY4szJOŝ6*3?K״} ooYi=$B!BusqP͆=3z\bkg^qZ={7dΜ$''Ϟp}mرc_ʟg(,,$''0ظq#:u"335}u]x~Z!B!.ٜ^(2f,bm Ʀ`ɛ3tNUԬDD7Ms濟fgĎ &n"pY_Z?I3ꁏpœKxXKb⳯r{'(Y>ķNc`Z>~>_ GrgMϒmǻjl/rۥu63}EŧsVFjAൠZ12? aۙ?}, X=^>/^KVRUp$e3[}t{B !B!8+s$$ޜ*;=mۦx\.7W]u5f[oo, W]u .$++ UUw(O<'NtҥK͛Kuu5}ݻ u]gڵrg!B!L[[I3(D` Y`\`$Jbӿ#sc"$J]&>}mk; 󺭔Cщ?xw`O`8]d@ʡo b!/?;UI\)o>ΣDg`#s_ʥ FiT.`={'-[_,|I^YY(m>kA 毨Pl׎7T o)eE!l[].F⤲p34j+ˎQM31B!Bq"!!! <Ock6hUAPGDP9GrN-׿ofP;yiyyxowS5nGk cL>=h.7Y/ !B!Woe̫ }] ]%wtV tL xJ#GDR#V-Czi+mgg4-;`TbwBWчVrgغs`| #a([»tbJg+(v%qM|,^_lECG $s&F~Rom-)<1{z9p~'<pUxy 0Om;'w[X@:<wd3_2VݽrPB!BX,0OB!B#]tcf-8fޝ(3$3V.N7 5L]l۳jQQ7> IDAT"mCSBٿ!)R,w}B/ ػ>÷=Ɨ2 "Jzʫ j3o̎t,fCz,+Lj(-~%,f1(4wwkf~(Zdm3\f7#rÈv5-a[0AĝW,\Zp%e;AUVDgccȤy1׬e"z^~lF}, =8ޕ.װ~`e^ Ceب^ܚґߤB!Bzj:VdUzIX3YfH@:FWc_E?`5)\5)c  yYMˏa(\dNLl_!g׭n5 <`& SZoG?3ֵJ}8=w|"q~J렄t S.'_1N=o٭ YVip׋pyƎ_)$%츾=]/aʥy"Q|U_VZ`YtpM m>. k-Z!B!N|VqR _]ojI !D|UV.V Bqq)Ć8))F %1cn/>r)]*Ol8<I]' hTkjϥ#30+* }ޤoݬ J.ѐtmٝ[בK~;ox3O| S#0ykYkE FYK`1GEMd L ,%w?4nIj+ ؉M@ԇ qCygġtK"bMzpxw?O sl e !B!ĉ]:_>Cכơ 0  CǨ1kը*c%B4a;Gq.ns XN:V!N,DȏН{%<*U5*ׄ(?+*(J}YQQ(_GB4\ !Bsk؉r'9,8!fKW!Nu<<o@ !B!B!N?I !ZQĞRC!Nb3A !B!B!N;I !Z5+3tXYk*$B!B!B,!BF3(t`#ZMQ@ {Ve0^B$HT0k$Q& B!B!ⴐU]UǯK D#&D]hFbG((7pJm| ]fp _'Py2p1 ˅wASUk[}'>3&26e~wWs% v"B!B!8-B:՜0zƪ(uo;7؞7bm.nьH0xӶ3@пW4CN~)u<{FڰeĢZAQ@QP&,I$en8^s<<3tP91&,j[|F!B!BqHXѢ]TB#xlfO"Oǥx3Q)QP<qUL kt 2wjՓhy& {XZlc0;G1- 6$B!B!BBn-hѪu'z(h\)QO 1~>m'nSMcCI+<>6_ƫ+nb[xn C8XfE+i)VG{T;O٫0B}<~ ؈.9BT:!{=coto5(%%+On]JR ;GשظFmSa2oA4@1ӳo24l(pT-yrЃҨp:k))B)I7^g~UeVL=lip]3z JkVjm ]$n*_щI !ѯa$^c6rRUPZW׶-}q.~ O߮J7Rg:4`BO+qLȱo~^C6>WX˛[Nf0rLgn1ǾD54LV>cɊqQLn"Z?NZϱjfȨxE:>mلXO_6B!B!BBB6gD Jine|:)ek+(m-*eeG=n_~~mvT3s5+J5N%}RY4`(wA7Цګm}t6c4Gho:wܤ[KRp9YS5mXxƎx{ ۵XGzұk|a?}cPQDh;Vh;5|A@HUkeg@cD 0~=X=$гK(/ ߳,LzUIK13Oxڟ]g·%r]ﳲTB!B!49/YϷ?VaLX,mZbFQғBs Dap6sYrg&qܪxa!qi.=q\Gx5Ug ZUፈ^SNMmhx4,áPT 4ɰ7߮XY.aI~Y F[Fm7Z䵕q5 ЏQ5ZV47]' ON}%n}j,_?`~/Q*s VO.d㧝~bLLI4.܅I1k9NYs1XRVYW!B!B!Nyt^aH]Akr-BQ !,j$Q!K(X]뻏Nzi*Wv ߹-lxjSQaV%_"3 ݬ΁*Wòa<#.=llSuJmҚ޶=NՕ5lQǭqs[Z]GcF2rQu~/dnlO{uM*tatֱۼF43 /wVME4 [@k귩EF-ŌK~dVǍy'sĘS7{\m'i*&ʪ vjd4ۙ8]RI1j92j]澉<2Vs@V!B!B!8QRSaW8ds{{Jmŗ<3"I=e)cJ']'[IK gl'P#ADY)tQQk4])O=65_vA$6mGU]'s{0qipڪkWczUߖX7uסxoiv6<-q5ش~{uWwt0C$OfB9}B S0u|G~GdT2'`x똳GZ~7 >i!dji}7+BW1*N3q!KX"@u77[kqbZq'}-u,<>< UN>W좆Ցٱ<ܔϣMFYtbqv*Q_q+f[}8 h@!V|,+K;K~%9ǓB!NoÖ* W@~Ljs'Ԭ=mLz} 'f5KBqQ.tǡ^cnz}Y5 ]GCjV'tpO5LbblNotwcիq |bjjjHOOgڴvm(ž={Ni{~~>S:7ߤDnZ/9R ,`ժՀBZZ*m۶X˗/gӦqjK /_8f~߷Xk;vq/w 7Dyv.*._ܨ<8Xf^Mf."$xQOC CUM(5(Jϊ R_Vu`?[ c`Gt~,^;=2yl;rVUS%6F0/DkWTI hd؉r's@$4gKf+B6ixG\.BCC1 믿<, 6={ !!Q.=Ǐ0 !%%bSNر^xc^z2j(\.۷ogܸ+9=_Oxxx6Gxx8wug7G828Pl Mzu5Lav0I:L~_^y'2ϸ",!gb$y;=6sY )A+$B! ax|$O 6 ӌ`ٲe rx< #7uu> /PVV|tB!1gUFh>׷N1*B!'v:-YCe:,[VCraxCdVo֬ٸn iSO=?Obcc0KT7nݺSZZʚ5y睙̚56mPQQ%11t֬YKnn.s|ĝwqdߏ#$$Mp]ʡq`ix pS OU9m>8~y֗B!2:n1ۮ&&yuJ!B!8agz ur:Ӳx6_^^;wb0jԥAfϞ̙=sss @l֭[墴EQYYY{qQWWGii)K.eE\v(l6&Ν;PTTѰoRVVvB޻w/eeeTWWrJn\vn=o z-119*A:+frE3"I%'s̠\Pw&S:3tP΃>8ñ=ߜOPߝW})BzTW ߄7h fF7T T"LLL3ܓ񶲣/l%B!B!ZL IDAToaaӲ >6l M|||㺮3sLt]rq 76lKLL`֬Y&SRRBtt45558Nf3?MDGG3tjطo~#G\tnjj6l؈n'55ɓ#33a./|>0\~,Xz@@W\y啭:aر-[HHHdĉNr2tʫ|iMv~:.sT~q+Tj n)[!B]먆fjrh.NJ波6tLf0`X(&#W04a+937Q HW4, B!Ygoii6uhM%-zkq;vbڵl>M(-uMM 555 |>)oٓkRZZfcРAs1j(^<|l۶X&LGycƌa̘1cZ?~<Ǐ?fbir[c:tC eEQ;v,cǎw̗H#&3.~'͗JlBqG+#?:ͩK fn;9l[j (٢0"avPZRa3xG6i~fH?w 3NlS'--M:M!B\jXӊ[ժ2dHKT7\UaV]>??1[,/_sOu?F###x晧y晧eԊBQx<+g¸wj ɫ5^#Cp}gn9'*O`1ק3r>w/:N0e/꫊]-[ *JdI t S(wߖJvP"~ 3x:FpgE+Jxga5;[rd˼wLϾ=vPYfV v6*6t jǠ[pzƛx}P+6:!sPRP ߗJ'Qjk%)܄`2lp<7t %ŪSToй+f%B;q5(8`w ӊa`M`Q_* CDت;f61´+mvw>^) z3撳rr u4dB!Ba.32˦MuHHҳ8 #-ę{u#߃l;u)NGBl:(d%Q:Oa|wƓ}V*" %<1(=]1p[Y̍. 33m\<ouˑc5ilX[ m:𳱩X>KQ*dH^|U1okXL8 @Tr-Wb;`"=52Cng8L_a6;'1(9O`Xƚ.sV? *)BlZ4JbZ>1_{Y,w$>>SmGE+FAlJ$' u [0)Ŋ@9u\}Gc'CRy8oz^}|"FQr(VA ccm)gj,4kԸϽkXyBq~zvJ:3d_$:tb 9v EEQ!RI ]gډ5>r_d{1BP& k{JH?-Xʊw]&"B!IA.,ZzJczҢB6o^G]u9=u"1=QYZ9*tX%|G'&5DB{mIU}+lip]3z Jkf[~jKv_L.+}_ºŨto%n|/ &1'Ƕ}BO+>1+6r[7Ё|f0??RBnJˊX[XP } 3eڇÂ#u9Yy]%\lŴͺRgסp0+ryyӡdv k+gla5`rL}Yq̸=vt3|ۥa{sK,CSJ0Ά7Ku[V&c[vfBK纡@n:^|M_yZ &@rB!΄ ܻ {2n*u%dk9e ǰ( d[qeFƬK>ej>*[L^04o%S;oR)RHܹl.L?U:E+ѓ5h֪E ZK!Bq_ԊaԶj_[A.vHNvmغe3=/:L'oPg7&a FQ$v)6v,{tkccJɉZ}}ϣ5Eho:wBzT"NCQ[dݍo`eyh[t?[TL.a5i4Pn6P, `v_97M!)3:mƦi-TOlQ8hv;3ȭ5ŅӼfߪxKZeE%L>>jҍ0m{5 :fjVjG8=134?W}..q?*?6Υv<~t[^?wBq5\ƫ)g ]GP(E#Terg [࿞x^ݹ!ѫbq7d0”|+~8s/[C4 i@r]!BtV&S"|5X{ݴtm(BlDl6'7ª *'%%<TNHHhvv8A媪rEGώ*5{VXXgZ^^Ԯf///yչU{;8 C!\j{}7FcLBFnI+q!yB("apց{޻?jgz] 4ӧ99_=ϓPnnnN(&s7!jD~'_D+IwK1ÃK=J*׿f;%i|4n;v 19GO?X??Gigayh@p1GU բL!L> %?$GAsF ;u0Gy7xOxTV/~DTUf;@dI@ԻU%I#eL:vQzg#XX'k>)S og6]{}}S9_nk^:yN_8?(]46F4\~6b-F85b_oW0AC,+@(<=+,tt$3s}@Gf$st>^m?cSV9vf昞ʄ<RCd`aVEa!QnBAeWL8XoUSoFiH 6Ͳ#L:?HjK:51PݗǮƱmcn~65Av]:c yc>c_}]ܛ-@ ^l^Rd?9JV%dIlR#,0k$+' `! ,Ggf ħcceFIVkak%Yū{)y&3i u'ޗ̧{o>EbpwAw aqrz_׋e|aկ޻wA?kq8dfd$l ]@ cٙ2}eGٲ_auXmyAj~߼j4IPh)IvXStf\8]W/ςZK6$:upD:8M#"ıw1}a`~N&p2gz"(Ng$sU2~GxPgQ{| ,8̑n$ΔQ|<b]@o:Xq1.M|;&sE=aF_ǿNu2%ŗaWtv@$ ns)1l\wy2sɲc|zW_:%v..3cg4럄WWBl=Fd·|%ؚ_'sYiY8S̟brF8.CP]{n|gg`|G7Wϵ; k)PI t0:B,h/eҍm&c{4b!guKLs(hZcG $\?b??t;$_nkƄ}w7D@,с >LD_;^e/~u=^/_Φ[(ۺkOlcߊ_?gW ڶu7ĉ)@9zl%H] MMEE>(IV֒>ٳ'~ǎ rsCG 咒v'OPLP^~}B9$GPHO(k 唔ԄرczSS⍀ǓP3&h4qM=r Ip$J( y3uϐoVIK翲2%'OL(8g;P/<,SHl$9L&يY7cT$)X &,޷7y_^DFCJ>r!kNdr[+%OwT$E3ަs!֫0R2 .LlR08mey\ep0Jc~P=eL:.R0* Xw{=? f i PޤukCG-잞M}^GxffITn=z{ /=tz`-ۇT?3#XMq{A{z<^֭(@p!]_zM`f\:kFYTtMCӴο*-v;OپiS3ǎ>`F2o^)/>땗ߟnS^fMByΝ;s>*Ow8}Φ\Q_`5u4$:sӗ2kũZ-$EQ0)fd TY'WLES/x #ݣ;>7_zrz:˗AG,U'XعFa~Z_s+ τ$f" ,#r_I d%.u,'F._kk(5`71(tCىǙJ+FgY]ͪ[̀E];ɓc$@)9+A HG7/ y\~H=4s_3/ ;}n>4vDZi´3G[GCz g1bĈ!nFʷh|VY~¦[xq1|U,ZkvR瞻]ϲǗTTVQww/8f,e,{|9Njd4ܲ8RQYźdTܺ8!5kqqzZ;U/ ճg|8ܺ'̞UJ^>̞YJm]=o wA>.d "z\]5q'8ݡw]|涛NmCL\_+-81.-8|+.-$P$  +ls`R̄;Z$rՕ3޻,m|r_79圜l;JKPC7SbT0@ev2e+l~FMH,Sr 9ITv0=ݼQ@ 6\~?"׎lґ$R{8IH:d-d$>tEey4;x%)siGE9TXJ6ϐ 8."e<]+NJ 9? w8odTJMc峫ؽwA~s63#Mon6<|~?>M'͊'޻oz>**wx3KY Ϊ:n0n(j ]E9sp$r(Mh,I) N2cZ*d 0e#[qn,ɴC\z /H|EF(hHo]?bݸGL6o/>M}Iue(Ymu$]gFJ S^О 209a[#M2@pAPw [̠lv|`);{!{]wn֭߈=_qL7fz҄̆{E?㍇yELWc332= ʶnK/vSu6oS6j_|}`ɭ޻ں^ۍh\[W[ÞpגNFXX阈vķ/Gff8Q@ S!$IOw<@y5F l2Ɍ%*J:D= IDAT$* ct6hلO3g_'02jboB2rȻʊ „'0@ V=e!9Lȥז+yglQ@M6^ r2ȝNs&d!P`X;_-.KN.%@:>qsm;?z$d KwyĢ .8YpΉoߣlv͝5k)l㞻n?slmq8/GYOmLJffo!Ne 8vf,exi1(1o ٳ'@ y?LTiDR;OKۆ}d9E7" K=#1K9u;+]5kq8,u.(]ϊ_?o;>==$e[@ CcM&7?I}MeiiCS >7HEShIINd$ m—nYB9Ivn,Q[[K1[<G)g'/ʫqV~m~Hf_Y^Og0_+Uq# z쟘Q4YiOubUp-֖I6<Ηl7FA DOB$ 90C 1Iq iKwy꯵j'?x0%Ԧ=?_>+/왥qa;ʋOȧ?7WOkߞ6+ƃ,=N̶;v}xs+@ 8G6u oK ^Oʑ:' fL 9Ff,#b=_ h ڈx%e;f7uzPyuTj&lANhakx*3:>D!yH5 [W9(>I,C;Bjz +(JXQ疌qSIJJ! "HY3&s4`͔9YY\9xsG ,lXF1$11uB~@p]&:ٞv)#D_x`DC`Ԅ6K"dbhRt#7pDP5 z &VdC{1Q-"t0X@o5csn :2V?U"8W>G~?-*SW>{?!Z C;̼ 5#gd +@ \%cd9HN^B0XI81aT$@(Jf(IXӑ$XIA蒄 ":"4U#! H6R\V:(%. OS}՘]v22s .b\wyF\zZi [nn;i+GJc[vmWL̺*[FYMR=7ACGf,Ÿ$h ŽGyd\}oo H&]Sv@I䕺%<` )L;)J*ǽxRuwqI&w\b!ֿQϋn`g_c?Y";U@ -M#%`82.r'&!*n'd`Bbf+fK$ 4 ISQ(Qłf2+N[hF'_ {Sk]|T R obUE$ 7#Nin\䕚^b9%ow8INٷv-| G͟i_IqRwRW?^W^:k9V o; -P?:z߄nu7`MHiS @ b:2zKK HF9+#Z>J9qd&]!+M&D5s*!ON2p;+?ˋt=e&m˲ւ,N<_s'/-SkI?Tq'?on7i'-!f3_ *Xz<ErXpvDp(.SB X8˵߮mϣH7HII57$7c$O' Sת[#eg__9+.Wr@  fIz[,u fjDA HUR)Lwi(Wt篤kȚE%5*&4ل)#e"&R/tnj ]NKGXcg'븳ؼ~:zrM,,N呲!=3SsG2:o2q;$t;{/Df34dsC;Zb<݅n=n&s+I; _@ IL@p> LR4Dk!oq`4}HI`d1Y$C%$'\,l:^ܯ1qU9 -R{=~O F 8s]ufߜeYUB2Oj@1X]xGUU s,#K\o'$lJ3F$C>^?%s[K%Q&TMAӕ0QcSX(Y庴89"H)ȪE0k"rdB XetEF WuQP ѷ zl_wft6)N;"qݯMԝ+}ziO\Wga[Ãs7.iKW x@,#?a*{>G"v}>6nl {]>>KEeU8vJOeL1z>g+8i1x܀6o{ǭ[qmݴy ~μs]W @@p~n:qȎ4,lg¦W~4Z*Eœ7CSV_XrJQfcn;e$Tt/kSȩ=(,hmaN lV^sqW )=l;қfc)HW<-‡Ǣ,ee>$J8'*]j;?5XSkD{3zoLi6vڣv[{{s?dff/Eŧ[ pZ1 @pN]~4$-+P1XFV5dɤE#ĺ&pXfĝ݈l#:1 4ٌ(,IhFڏCu b Fg`R5Vohx)pھONiQ] ֟P{]S3t/d.)~!yfNψavZ]Ujyl{c]_(; 8- .s  oS]~{HvϠ9_sl/+5kdff7{nag淨ͣ?4oE p-\0(~y}R2}* A] |Ey+I`M%ob>xa9 NfRd^< Ğ3 N_`?}3d& У(/Hw1#X丘k}&0vOuА%@ ׿f;%i|4n;v ՉS1NYZrdtԾ~΀G` Oi r9*;vN;YIL 5|N;V;߄Pu"&#EP&YGRt0iMբmp ͬȬ`?69|Ooh&+d\L;!Ʈbu@  L _y큶xlya5rV4:..>Z>r$CtuwJk}k=KF2)ztV|<0" o!*u>[(خ_m޻ϮKf̋vGLFmq[0Ϯb9,uqLzZߴyK~wA>'T2`@ WįyB,h$A椙8FÆnӊ‘8PudI}ͶR_V[BԩI޽]Fgr_ʋ#|eIe逮2=JlwP"Ʊmcn~65Ave 5T&囑f&08 _{ssLOeB !҇&ڻ}U-!jU!1u {ZO%1"@l@ 8LHNk&)'p$GUI@Aȿ+cHQ j@;pxLn(zv;GfqˊEIR)-FowW tkPK5{\i RCm Q1ʃ=^~xHX7.|19`)?Wz\yk7۟¨$FpZ~P݃Qk_Ѕ߁Ө1u7ąE ںzJOxx<^z>8vjꩨ :*vi{BR ubiJ;㋋(ۺ-A܍W*.l.|lq* g[pmчx@#{{Q[W ;xwy(!@ >X 8ur/qWSQAV~1zmmePwaץ$/G*dv#!uVH$1O7;y<К|,Blm Buk m2 eG>kztD+G0Yl9G<)2U{M!{<5aWbԄ n|YNgy|+⥬ rG'1J=_>˖(Q[k;=֡_XYEVZyD8Q]߳c\b#dpxD:W4Ռ:x5lC"a$=(::x;l?24z;"K2$vpͥ3=Rntô.KCO).q;,Lm[`jMP)ø)}E][$|6;Y~'e[Bb1sp{ҧ۫Gp_"90mLx 4hnoO<7_9dT "0Ё$籩z~b,?CpʫCLx"DU .JF&70#|=Hp?,Vf^) fM3o6qWYmGy4c0\oU9S,zαnm>4>,`_Cj DπCY ~z[YQ ">DISL@ k2ݙd;NOD3MD5 k*rDF2K E :$,3l "q2M䃺4ɎY ` 1:/{ K.K:h*ETD =t{WTǔ\'NҢdoR=C&J Swd9 'Vk}ܰ%x(/x]^=]SЏkۇwxe5xC Ak/g-,Z p}7%,kֲU }xYrʶnٴy olv͝^ptZʶnc-̛;'NwA~۷bn-\5F%.rw< @\޴y ׬er7n /E 0ojXYf-%Ӧ%1M9p8̛%-YWpYf-Kn]c9{OIJnj(>3ǟ.(` X s ]8o7Cl۱~6@Bu&'[Cp:JOE29(D5t$.2^\2}5Hh@-dfg"F ƈ!7v )aKkQv]rs_ݑxþʻD[x6By1aS>V51ٽգT|zz6mt/I~{ǩ͞l o_o9zFn_kLLjD9ȡQL#d;LD5cͨzE2DhHa9bi1(zx"Q+Ɏ0QejvRӰ(6LE%];foч<W f^|11/X_XM똟)J׌<ږ(n0{xlblƎ/pO7\'K =7ޠabmQ{}xJ'K/jjΟ^8`ێ=)o {3u|jK˜c9c0.ll':#NYb4ǽb+9O1d;}[c';iKG|XgIB NlO(f}G,nYSX 8Z+̸ 5I|_gW75~ 4iIab:o};5k&QYUA~~w`Xm@)~ pY!@ 3(.M!8dcb>y`1̲!˚$iH)w ::3#(9Զ 8h =jZTO3 1_ Ci1h{8{v7N(8#wޖ-̱s J]H%uVV ;3K޻~!ⱜi}m:X~Cب=.ݼy7Eff:gRVnƄ ⹄cchX s<6n P__$nq% |^B~z #E5d0H#yd & k?mu`،˵y̿@pY)l4?f%YbFhIE֑:_$ݸl eVn-3܇ *2>d" cw1 a2,/$|d ckRs1LN Sr}x>Vާpx=i=`;Ϋ{r#585AԾM̻w}^15_`0O%{n##sQc<׋6?pyX*֭ e[Qu[|=w[;T[ܺkֲ>L3B+w{Y|v+]ﳻ.g^6a<=Cc߽ +*x}|9@ BaL;k7m6${ԱpF-#J,IȲĤٴML5 /L$M z f%&! ¡Y5Z1GØ(EVȒ,(Έ=RܕT¤GqYU\0XU i(t=*'\mV^D}4K^y@\RKwyǛ[C05AoVSsG(nm!ٴbg#˯u@\NeFpG|BցX푸lĤ lD6Q>Wjg(ٳ`|qQB~ /XJEeuq/ܞmv{>:3K㢮]Ιq,ZvwA>O, w4ulO$xnh{=333f|S 7d@  ! 8~M֐$!IAVX6+M&(|oR~j~&lf+4&M,@  ,sی n$Mx ywl1hb~姘H*ڕE 3n/z$J!"E& L!*$1@TaaMux!giQrB(z\T)?5ys0* ;"}?]W-<`(o SJ LN΄p`xWom>n cSŤg'peWDzH'N=6^E~Zbr,7HQB5y\^s!:os8}->W6۽`=\c!?ob2s;_}_@  6VL@pcZ#?{?)]u]%E7nu]Ct$ --c.j5jiIvi:_̋;xVQ],@ bfb KfdPĄy/- WO$CPd,ijJwoBnDjNX:sE ±&*V6Z4,6hwC/0i|:_ۣ]9&RRRرcEEE؁0F]ugh%KKi[`]@TRιeK+vR\\LLL k߼c.6LqoR9K\yl7ϩC+*5]PT5ZR۾$jnF[cSߖ]~r|tگHt}jR }ǫ8} EDDDD70 JS'6Hcȏb a1qe lpĈ Cp Y4˴:y,Ooc.55Tտ\v/i{{jj4g@박{}"$91:¿2e=ŀر?3'Nk4CǤI3g`&Mr6l~y|…|7ͰaÈ-xY<2,?L +iXSe¸.Ĵ1w}zm@B2_78Š+""""""aVfDȩWс?Ή>GϨFMo߯Q3ӣnA|r8;j:95~[Dߡ{t<-jYZo?ң{8ϝ>YGq/4ׯ/)) .0>`ʔ)r)رq>eѠAk >l]MDDDDD~ ${cFvL!=IkrK\L-'04h/-wݺue 0CǎYp!/e˖|ի93W_}SNԫW޽{; HLL¶mnq*mPq {RݛJEn}@DDDDDٕÁoK!*c RS}j6 ];I`MW蠲ߨ~~LMۺ-fufͦZGM[ś-?1b"Q[v'eFMϝ5]\ 2qѻwolrr2EEE$%%IOOq*x„ ZK.8=zУGfϞͤIVZDDDDD]QCqM """""Q ߅Ykhݝ6T&:c,"+ԤI5jO3/>>b"瓐ah"-ZDnn..WpB.hqqq;0s,7oVi"""""r|<ǧF"""""qT{o2KYڭ`q)*QYl6%Էt Ͽ[޻~|>&L@Y|97nbСhт'y.hD,bĉvi~"a B!l¤IWADDDD8/u|<4Ɛu|+PxwR ӛٳ*?n;<>3~jJ"%%(!ܤI ȨP^~~>;{y^n6 i޼guV=E?~~?III4lؐz)Sп,⫯b֬YѺuk:,bbbm &0m4JKKiذ!\s iVRxg""""""""""rB9A֤>;U3PqIidwkMBFAb.Ot1ʩ<:Yu3節R?@T}""G_BBZb׏'2g.R^/o| C aL> .t1 tݺԭ["q'N*#%""ѯ\˲۷/ 4gϞL>?0`6m @RRODDDDDDDDDD~j؃IFJBqAL|,)3jo#q=׬]ضmXswΝ|>"||{>99P(DAA)))XuP, qz;cgF=EDDDDDDDDDp!hTAz8dO àNn#W}jxǣ}PB[)ɠ>G(޲p:fb-ppr!gD*mV0 Baaaa!ͣGB!^/CїH˦M_A7D:a4MLT XDDDDDDDDD8d'F K(+ٸi*7n%U㕽M.uUX!"n/Yyx rIR:?7]'|5 rOv˞d<)nL'bY[F 7">VOx92v l޼S\\mj .GvvCnR^SLKN3y w҉@eҿ_Td& dffQN<.KA`m?+͂J9ͼO&8"Vv5?ϼQG#ll㌋4Sy#N3^cAKx\K:dW7V[z  wJ< WS%68_Dx9v +V,c۶deէq\.Hd˲(**dժӄEDDDDDDDDDδpa`Q1-ū)ޖ.ں͋VQUv%Ҥn5OrË87{TBĬQfۚoi]r'D:NRѺliN!3m6ϝf+KDB+/3[L¡^g8}/HNiCĴy-¥p>v}lٲ[ҢEKnGd?.Xxԭ'5""""""""""̤*²-KVr[4NMU?U+ؚїLа1-w/ocl)鎷9*6u6'˔7Gt8v\&A5t7CFݕB9} ov[6]o~L~eteuw7a[g&PwӌO!ԣ\*p(o>[\Hb|*kLˀb;n*Ÿݗ;7tM¨v{7/O IDAT6/f@LcjlO\|,;/uS}=K9/[ln7&)J+mA6m@qݔaDʪϦMU+ۍi9""""""""""QĵU |n+y-j2HҟN;~)kK'2{: TgoM~'Ya3kr$u߇14f!L1q+0m#qz#?s; X4}9qy3O]Ś5&r̜ O)+P6GGǢ.xbK,scv,[ kU]Uc6Ww  NOA/'%q?+Z}-..&11I"R, q(""""""""""Q;ZGC.;\0LS}2gW/|Dč+qg!5>Y^RNu_* Bs6'޽UҘ~jM'n?:4iVqNV/]]~kX)/-&=CL˨F,K{?{Q}i/Ê&8adF4>gZƙdee@ii)7ndǎ?@-p/G/ ^~ xbKp%0c<`8%-A"6ND^@$d0X깏Վ}>L&7RfD'𤖇lo>f=>fk2Ѯ!VfېRur.ʥow{ڸ9M$eCSE=b&|Sz̠UĪ`oN}~eC e`:}^މ # )mVIg3<"FӋQ'S/ݯ~FJ%uɨxNz{[ טTzWGzmh*;ǟ c8 QY).M`z1gD.sMkiZ@`-oL_ZEDDDDDDDDDDJǴ;YéAڎS# Htj)_nSzЭFkƭ/E:sqI\ ⼎T{?e4i͠'2kߛޝUNpLJ+Nư=9u}nꂸ=/4Wё}w9_y^Έ^xLkKZ"1Uԥ*3JsWѢck7{۷ NJ[JtI \Yw5'V0j^odMz1g'sIIFRɱwnDJbg_{gjHqʿ88c8؎k>۶pl|>ƶ-3Q%lvԕO_6A3+VA}>8aJ sx[YzGtV:Goi5u(mM pM_wXaθ _t\!hNbc^ٳg1xP]u~E,"sر3eeeQm^Hr;Gv`iJM."v0BՔ4HF9!=6mړ86-:"!60MLۅaf0a: cuh(>i}Ƀq<>*aJeH!M$Q-7&v ik>_ 熷e[~I,4%M,P;q6DZt8i+(3;㠚1n6ĿN7CW1eg }>+eRN#$5g?"5m+[eOᭅ$tˮqz)~A8x3vLJ #;:"""""""""""/EDD #GLh.]T)CIOax)?̍Tg_rѨb>:Oi{ \xqdK<:"""""""""""-EDE6oL ѥ|m??V@_}fEN]tm;ǃ%)B A=ŢM'`)OHZqKx xl^f>% =+ms4=F4vfm2-V. ɋlZ\ G B l{y-F=PfMd+>dA! v;f.C֊18z9v XL7 t}׏f~]'G+/$۳]DDDDDDDDDDDpq DD +Sq܌& 0WųO^$QWL7ٺ泉at/>İK*F 6uОiKO$3fq@[gd}\",Bgc gn6Jߞmqѝn BλVl;(:'EDDDDDDDDDDDEDÀoQ~Ňd\}A̞Ίl㬑֛~)s ڂSkS 9FX~̬Fdl۹,k{ҔtBDDDDDDDDDDHӧ""GRL-@l8MLDsao!ԣof<3vv# 9[f4[dc@3W3n\X{YtuWO3PEDDDDDDDDDD4ED ; RS!ƾdy;mʊ%Ņ6kY|1)c/(*+lQkihk>9S{ҫaOnΗs;p~q_ #"#:'DDDDDDDDDDD$9f80eFxw|yDeB0 r׉e]mhs=.Nޗ~柙=|toOW k5ǟLkޥ84?8/ڍևB&>=MϷ?&ȡS`#C8m yVϭ nb`PTPX7jQRvvVA@+AWq] vNW_-*Rٳg1xP5įeY?;v,B[i#.+ch×?9, CF2tkfrjGXon85TAN8~5Mp\j_{\ta&i0  |01 ʧ1]aT~Am""""r<8y99)75ּ@LBZ-EZPvIHјpjX@` JWEeXH?p ]EDDDDDDDDDD"EDD$X(ڷtyk؄KwmF8tf!Mrڡ<0 I[D>9JED2Nƻ1bWaz"*&7p%+r-6_#-IޜMZ^"""""""""'EDD2+e}KhO7~kL\e1R1c)XIBWNrQ ~|ch:;Ct͎~xLC' B`cpavJ\>i ,"Ge;l.S4嚾icVn rϰLRQDDDDDDDDD肈q1cp<8$p*+rX<+i,OYΒED4TUT[ԕCDDD~6 [ V%;->mѳq+cu?Øsa:,|^zw)e1ٜs\ժqkswPO!Qs:-޿L<嵻P)Y>)X)2f5}3NdNwYMqA("""""r"RXD0jzm%8y7OWq&ĚO.Ubdx {wP1~ p2vgGU}q~ӵ}F/fʹLp:XMXSJDDDDD-RK Q^A&|>/pfdRj9NXl O)t]U{Lx1ԃV|O=˓+rnO*>Ю{Y>]l IuuBӖz ,Ɵќ^g^`jƍK6-A|f~9 !X;Wy,i_Ϻ+4ƿ{%ⵏ`C!v\-kydcXj^=t%9a((N5#'i`/M ""Ǎ/󗧿c@PWTL_:mM30 c/ɛg p /m.3<5 SON'}?OpJ;n 7bz 7e?$ `ocьy,۸RbIlgώ_s;:1{˫XVm?eјv{2sm/vpKRF=ꑕK E`cɱ0V`c_X!Ǝ0Glz:E9S0((Ih\dP{sV5KM1ĦR'%\Ϡg2p zWʮzƵl8OiTA1>[Oبɷ}H|?쳲^7冶> {;ӧ/%*F]:oyuD<\y놎Ē/gd^pzӼkk<{? p5oGzPZDX(XU\c0^ `[v6ӺCC9JC57#/]u!MϽDGQt˷l1mN&9 <^7pujv8U;.d>&b(ѨٕlJbQepveԥiZϜy۹Aar۳+!mcp)mx{ gwy6]ȉv,""GGZ\_÷K|q8U|L11ħo=luEQت'a~34HI_Ĺݲh۰.θuvX\إ.%Ѿm'}>߿Lc}Z=E-:}codgpVFt@F 8x}c._] oN_Zuwȵ;ELD~=\1 '`mU:W3xꛝ؆~-HD\/d׋ؐ_BiI>W3i1p gmD~dlev eD 漋/K.3riH3UQ:?͔_#Kk;ז'X(,"""""rbQ`lŜt!>3˗<IFD6{9!ßp\>Sը"PĮZ\.IB=?P¬Ǎ6ý;x;7Z 9̛3-^}q)ٽ[fnf&8;R|]g8;Yd67'@Lj:GΟGG_s}k_6zܚ?𷋇p"l: Pj `9xp-7ևd>O\JKl13߲򞭆']dwSl9=#qW߸a13p>wqHԾ\0IO\ˌ缄KʈCa[>L9~q[9DE%1rXnۏszI3Dv)VͿ}K?g0k//A<Ч""""""' ‰accθ'ǁ13RaZZ{ҵ*eƊ ݛ1C>J錛5y#ZHNKoJ=疓 ~}#? KZ>]Oӿ<'=ҾMeI8i;#Fcf\醿rrN^b +Ϗg)ze Gqݺ6\pkNB%m F[D/FBl.|Sov+RHIO#i6 aB.d崢[9wc*$cqBeD=88P!wKL3y|'Lu;KX6UVԲ0b0H~׾;iE3ֆH @曼lm.Ȍv&{8x0dzCSuޝ8 )EDF*\?|7Ӽ/"""""rbQXDsm0 8s I!b*I0괍Cz4&kR6 .O 聗iߗ~I:z~<ퟮMj4d[+沤rh}6Vķ9#G pQtB:tq+GT,+Wוtj#E<> 5;~`! \~'z;7 JnNM&i"G4jdY3L 3vf ok.}s2Mq+HY>YD6F̚M3L^8/˱$7sr"""""""""+EDN0x.-pN~Xezσ+*Fn/ƯOq5}+׶£3E"&`?~FK]mh6'{OS$ 4{-w=פ3w+W]&$ '142F5Z+=s364p+#̜=rf؈UGp 7Չ$""""""""rRXD2vUZvy4T1P%oc̃sFuɥ_&SLI~Xqh7iٯl<ѫw.qӫ63yܩ 4y-{HV&<6aӴA75ٌ]|k1O҈aOz~NފFt9&^O,z}ZgSQ+z88U[~:fAa\>"^ŸzIScWofP쀵3|k3e4SI6. ӆ4g/⹷>o?浛/㹅8Hӹ,s)wS-\.Sۃ{>qN$9`*{UuVL6ہ *kP +ccE,,bг3~bNC4Ȳ ǹ/sgi3]~b.} ig%nm~k3t%9,|>yկ/3nߎ/|!y?˃lvVE*5)\ybb_7Ø+?&C;3s ;R♷o(,{tųm}GW$N&9f-U @o`ZQ ˩Aw'SIˈ`Tݏ\ɕ۠vӸǸ ֩hD.ӠWH""""""""")EDHׅqhO\?4 wt/+ˇlTolo,pPU1j3O3tCF3mV)u %b̜it13uF!IZї2Z'l 5O}ŵ/ %>ҫ{9_9y<L7>aummȆ),%o(RicR]ϓ}*,ȢMJ6:6^r ^ R&):U=v+^죰btő"""""""""+ED0kbV d2ޮk몗9eX h{/LChP+}3~b)+,:y2u}comcP{NtV'PVȊ,.-fMh=+lRDx"1z@Yjñ-^ʆ)r7p=qהX4Wm@"""""""""1ED 'aW|- _C+̢)<7+,bk8qSuzx0〙YdlW^w2Hu_9,㣛"7_ojPaQN"+ūHDDDDDDDD8d$7Z6l;vҵ6d64UcB8v. !wߋ[R75CB\mp!s]I[Z_fXr{GV1}&b[%MݒfޗYʢٹs_|2[%(5M0lS;v\VS5&?*OaEn]WNӉ#""""""""rSXDrck`ͣ٧ckZUgxW%HQ14spu97 Y_6O2Mn!>0sýmI+rk56i?f\3] 4>{-w.ndI^C?3 IfU\\ҽޒ(q}"UsI>/Lز X-E'zL|(b6Prϟ1U}{ }ښ=7(|Wgy&I&5u.k;[lcXp~?:AΝL >.PZ#. 7:OHk{ociZi- haa4l4|b`@0 e[ u?[{""9rl͈_Et'v>q`@a9TLN; "<>1&Mmdrm2Dx&Bv˫q{\_-ED&dd&0~`u`xII芛=IU),"s0,H0\\<T;>QXDp` %DDDDDDDDDDD'PzQB`""""""""""""""G EDDDDDDDDDDDDD5ȵj5F*_nlSV@m$"""""""""""{M`C̥\ fdǏaX`NzVf C6) "rJi7l$1 S$ $IA0!`\PPDDD91,c·rܣ}_o "8;k6gJ<DDDDDED5x>;@ XX"qv0{u,kK}? b"~y6ڱjG]̔1ڟ.k.-Pw062=s(\_RGS /үB>gUU\DDDD^ZDPm:@ɬ)sr/0a~"('@ KhuV7D!nȝW>얿㹻iF3䐗uX<&i${i,sz56fuq #l tj!ũqX#cRZYcՠIy~`yL|odx$0HKgǂȡAִX8&-% T[\o#:bЄ9uճ "Jݴ뉞4MԸ""""Ú cI"|d(m=|g6?ϩ1յvV 7dz7p:H{%H!4 z&q}bmZ| >ۡtS=STpǫ ބ?~PuOtu""U)x2e@*eגk=ACYPC(la OYNԸ""""Bhc¿pܷ}rKy} K+  E/κ h Wū_.`]&mvt˙ڳbVWep SƬw^3Y Rd%ᜮ 2g5^{_/iݐ)<2 {O^ѕCЌfb7xu ZxymR!OnR74e'kˆ0r:REKB̫pTi\y+eW i0嵐)tmM.Bp[gs{ É2ej. %93spjG63CN`!3K7 2S<|ZBLN ]'a^~u=7c!awgQua,x&""""G1EX.ec:͛IuE)];#3%ƌRK~N{mVж 4Km]؆1s wr_n18]n@XDDD wWLϟ]r^x&nén5,<tM+B2\C@-3u'|ˉ^UM-J~K Wdt㦫:Tcf'afwqXxh.o]#|\ڷ*Ey8ض|^?.된oOI %k*xjF˭M:-c^ulI6)VlfƢ:zf hR͆ӹ"V.,?'K@üŅIIyHxL0pmw.2Cّ̩Ìw}3&JWZ""""rtSX䈻vq|??0?@Uu-XM3Rէ=g8g\.(߯hpp.7N|mV\yoy=>u<_,"""rPO|lM׀瘓r;}So<1yٵ5隺Y7LdrM22]x'מڤ̼c?fʱ][oY[=?*1zd.&еc6uG{3}ݱU{.HM{/fG]Lۡ,'UജCͰX:>)`4HWɫmNdaDYP l{>C#3q,ѧ}#qLXӼ[^sC5'Bcs㥶doͬeԈ-qy}y6dqqyp[2ML\һum^۸g+%'/NF :$?0,8ud.WW u= kZMDDDDb@p ┓G0q7'7"+Ò˹?RUgm 5yx?jmhpwBĆ6[oe'An~ +KIdzc0Jf[]smvkg 'r}fE8KѼoS D +ѧ#-ƽf˗ѿsk6e\nNp6Ńqjnnڔ=ULc'>-`,~d(M{Ƥm+"""WOd²{EЌ A $&,_L]=wݏ̓|$a'e 'LÚ:63~-б]%rƴ"8w[dsiʁlm rR+& D~2?cL؎E3U׳rRn̉罅 Kt5qkObS2!ϧ_wqM߄n<}7c?=X-ikkO E8r,@Zc짔wPkf8P-1T.g>q\b-(E!*VDoK/z 5 > $[T2 00Nyl~rMvU|[7Av{/K|Jwa¤JVcu?d{f&bWtΊ'ޭ#.>@|l uxÈR~%j93HlJC Kߕ0n&/y |{^ÿ]!RW Ґ ē#8!]³_!<#]L![n7I25*\Y8f,@? ճ~uHio㿤琸p 3j;siMDDD~v5'2 ԅV۞׾dRE?NNyWܱǍo=S:[̺О#ޜ=";.`H_5yuUy幕Sxd,<&na55Lg\*hj5m|$Qv<53KX_̓?(V.|d SfW L/ [hF 2(+9ݼ\?ux +/ioٔ gwp c&1,ۃ7eU˰~kֆ9L/,NƐXvX7'F ȹݼ\7mwH>DDDDaa2[BXx&K00\ÀA\bACQ_4|`TzS?״1|;oh] 03axD}C1kKH^a` ڎ{yU|99n=Ik9rpw[u^]aIE ww%XJ_r IDATT#_Fa37o;Aٚ^DDD~J~1P 蓾㹜A?ٷ%tʞV^7t_^pg~_x;w_w Hnރr-y%ΏiKuczidn^$p̬f7IdNN#I|moYAQ rՙɌLh8=*>)祱E>C&)_7i-$pɞL߄ŔR:E6) h'} 6Z[ބ1I.?NIO{ۄ4;ؖEzr):Qs7_5 Yin?p\iql\q6UJ8.̚5G{`E{b[gBN pZsbN_B-g╌:b sX[Qgs[kYIL ޕs̏ǁ6t&Y?]d}J@n@zƏ,Z]Mm8I`/ ytg2uC?o|y[w[mQR_"'7|xnjc-عs7 Xzh'aibfw 0m` ?fpF[^nϊ0D?1+(<.;[ 艈=Ü2E 6N8L֖UaA1k+mə]I,+yӈ_]{3m4c5}Ww툺Aanm& #T\-~Xɀ}KxF͌㻏's1'Bi%ѹ!؛2+=;9j׳%6 lŽDiEz|pi5mjXQȌ@* ͞D!HJvsx] 5YÉ>×1Df >ȥoّn&}  YBկgۇ0 On17z:l3OR7n粇!>ʈ!@`#Z b';>:6 ԇ1A,Ősϧl$I*if2rZl}**dҪ=,eCV`ᴻK1~EsW_1)$&l' SDy0yY&YY {LcG xd}~OgwB`#ES6w2I(1>' ncx%T[K$ŌFz=`TTnbݺtv~?me {LjD6-gbns``b8v=D}>]s]o}{xÃ[ڊf8?1M@RF9`9BJ||"j"ax/8&,{!1 }ݬ:KGFqprrs|g,R̬dFtjZn3t5S,h#eۍm,/HjY)}"ҥK*'MᎮ0qR/B~T.9-;kxx\xj6Kyetq{8z 5`0>QX`bax؎I=6qz*k1BUx:IHL&/'x|~[02d0J#[F 3:yZ^_ڐu|?{s 8.cF:VΠwq=:f,":Ֆ2cMW6 95|SW%Jӂ3nMtTY6g6Y'ʔឝG9Xf(gfhD]9L(,rDh5 '7!k(fQ*,h@czI 3KLf3bccv'S<{Y(?[[/qmcV1i#I9m:n"ub3}q bI iU̪t [DDDDDDDDDDD䰦5#Ė Hؗu ӋkxencT0,j*l#݋ :G!7`7ճֶhEa *2ڳ5xeMۮtvN| 2]^|Y^ͺ?>C}i=E!VF=tn-)C}I=vLla"""""""""""rxPuBi gBck$B9ńMuڸ:>^c230æHMU,ܶa·5W޼0(5h>sR|0zˌJ ΧjÄ"! N$ĺ5Ǝ ))*ߞ]X"xviO0WU?!5)'!!$,edc/B<4ݥ|:ƅ G1%ķa}S9C:kB8ʥ%LZ"ӷG2%Zx 501b]v߫:e%z9[*y1hH.祹䱏XnFYiڢ5='+7OEC~}c;u /:..wYkA}Kt=~Η_Ma5dffPgܹܳ^Mcz|xcouyKy0_ǶG|k*72s=nz͚W^E\p6kCDDDDDDDDDDDvK`TVV)#GRPP0 4`(O6sڧ)l"alm58Qa╄Ba0 rss>"3#s>0,?ōoe]yXcYקoMN1G Ƕy򞿲|lb~\i ]u] b|/>q t[)""""""""""";e DoM4Ͽ/N\\a`&i`.&..`X` _⦛nd'wwW=l.Lqxt<u]vlmͿq\u]b|^^|!Bu l܍|q*Yy廢>SXwdb*0 0,LX^ ahhޢs~v7~c:c@l{>_nc`M xoC(nXfS9_}?""""Jt1/]skNKNeAq]-.gc7sѹgsgqk|?^;^er˙X\VDDDDDDD9d5<}򫯘3w6kVb`DtڍA3l0b^8,L[Y5XOym3!o2,nZ}]>f?UĤѼSF:Z'4`D8371fI?T8>-rȰ&vaXbvLtlϯ{!FDDDDDl 5jF:|.~1`6P=.;{d|^4dj.0 :)ȁa^cD +QSUMl|=p+C8 ɏwT?Ab&h6?ϩ,`Hz?pqM>S5o6d3p2r5?7Oap}T}w6NAݒ.ª 3}A%UaYP]k$$`;ѴR_騻%"""""r%!fRLcY. `\L^l!+!sSֹ|6Ż\ukiٮVDDDdU XPƉ_sx{lj[p( w60\| 8i&ko%솩۬fNyO耙t{4˿[^੷'dcOb6}.d4<J|>)aCCj^u.vhy=E;zo O{ngͱ]ۭ#`7]92wR V9M87}V9},e46~VY[6L7ӳ ;QL-E!VT$grN41(_O,ᵕaW;0HKrT2Ì1߰y1Jp*&OcScee=]Jya^k!%=iR87ja2V*o>2C',"rvM; ʰi c.WSX|ZIUŦ}[6k{m_SM=oFޠYm\ <;7W.j7.y2ޣ Myb8\N< oO/FCf:˟Ʀ-Y6_[4)- a}&?vq ϬlhIDDDǬl2fMAqdgK$?n+>t7w50'InOz[NbY!s'_C<}Őy}xwe _bFgWI9NmU5 Ӫ0mc;`h/Im;5-/Î#;T'+l\lV Q-gdrduuw^~ymȏY}{.ڂc aİAt 46N۴c71aIv{ylIlB~~ awS 7c:u}rķ:z5dwJ.dܦW9Niu&۴ fh鍰|m1oЗS ͛w^4?,XVi`jcX@| ywfg y6dqqѭD&&sݬa-.ʌ7w:]c\mֳDhqlwbrԖ㭙Φ1t0`-$"""""o9XAu\xZ59 MHu!35p4<1kĖ㎧;1yF-aԩM>m,bJڞӏOKp'0 `ʻ0ujc[aa~ G,soZJtd٪tC?flM""""?#o5<\M'cޤݯo/v ΈP8-^xw ʨ㭳E9`am ~8dN=e(Z&x8FfFfU찔{Oz\W[kYi^feġhZMغmiYf&MEhkegˤdsYpq=Y7׳5#5?+)pbiϯ̻,i]ar٫(5Axx<8(-rmғtmSCVG a޼zB6VZźxML?z3 IDATYڌ/<:2tό6=|TМ!.{9 5OsCeEt$~t"3+Z J)7¬Q~j3mΟYz[y3)+W֯ù?Gaੜ~퟼Q.>a|97^ъdc==d|θz?GF¿;bd0o CY񐓛%หš 1q^r}0pǘ׻$ rR<['پMv<4,Ԅ3ELul;p7/GGyRf|tN:G𾖱zq晹^]3+y ׄ{wou@Dh Cllkg8nJK s\&,(lLܰ3s<_}μ)|99N=]WkY,‰m=_Y^õqw>mȚ?WȺ.7w0"w(Ńh dŬ+vZ*#L4kيTs#@vSǸD}zNJwhFOY׋v2}Hy֢tqcKw8tLZE?ȉ-v1Bl$M6_55Zhu$ZPƃ3D2vg( [w'ʼu6t?[ӂOc,ͩ{[G?e@:r\pal}m[\\W.hq){M{nߥ &n_Uko<1L^ݣeCpt%S؅`?nR/b7x pigDDD,~>}d%p qz |r܏ߛIДD0D_ߦ6hemFGS: }E%4iv}jrc6Q3bT< ]3tEc4+u9W?YqB莅=׏o]oTzi1:үKtrlj=QP٧ S|qB]ĉj C5\whc0LRͻhe>,V+>=6}Lö4w0wB j+t?V K@(-+Vgp.*ۚPN`'Ua'Z@9s2:ZtuOk{Gv/Y]5m }N Hp{E =R{_vliV+kш!rTvLzMuMzk0IRIi"آVoext 0􀪡J[#Ẳjʽ*#FփK'h#$IŅqq&ŜVɕ x%0XT:hQ?d@"AP=FJQ5:G5}]y*mUxTUTKH MzktcS6 r"01J$\ejnu4]*)wdT[7WjmzXk]# '`Gq"m5zpyVo*ׅ'lՔ1MH%V;vGxP=P&xը@(-+'.R6ϺoSU4 Ж=ZoJTryDŽ':'rA@NC8v7Q` ` ` ` ` \]/=^~i9ѨN8L1h. `E+~J*EҪUO=+=1m:t-ujǕH$ ^ : `E3D-ڼRnU\)ЋJk4s>Nh)Vx07T. Z@@/[E4jT&9d.ҧ5}#fkY$[w@ ^T\1JꡊɽiE;ң|c$ٽZRe-:}f]LM3@/Ңϼֽ|)Ẅ́ X3X eXzj,)ҒDqEsd>}#4t##+H;r=L=}˶6˖hw/-^gN֬o.>5kWW-KtmrGho/ո?zҬQiQǵ ںهVI5̱:{/).IV謓cFkԘ:~ʺ􄵻Cz?qƌ>L&/L8t@/{/,C+M*O(ZIq*zENjXoqJ "Wߙ-|B68V8g'ǢB he;Zָ9Ūv>y\ݴjnj۞xfh}Q n +hYe4chcr><{Ӿ߭zCy)&6_Y_AmOHN_ܾVX7qbwty(!vdjj{  z˶oԐ:=Q5@Ǜ{f>C:zvKjuz鵚qŕ:y2=VB˴l81{"jц]ϞA͘2ITwOՖEW=T}=cr"jGX|I6j֩G8p^y7t ǨnC v&*;mk-Ʃ~3'kד='^ϻP~GO.}W[|\kF&EX ]v+>?-u檙7jkl"1E*J6WBEr Ç]9 5y@/5jnfmVӖ&~}}Ɵ{&o~Tg=SA:fN=Y-K3WbR󚗵֝O}cѓ5y_+Ć5*@u[_O;qt٬/l?sH&(i7tH&(`Ev[ꮧ&GhΝjh `{9YT}qtM禿j9s49h9j-+F+&`ޟ<'WW_}M^RC*6='-Y_` =#/Q^+:¢\}j~^ǵ|=ՉӞ]Lg7@/ڼe66N[ۼ} U(Ud.$x;HE%9cy>J?Sk93z~Ѻ˗k{S≮J?TWu˫(6:G.JS88y7e&` ]543&c Og]9Duer+zۮu]L~N-YWcժ4g<.H$Ԥ={W^ұs<2-^XHD[nScc&OU4"u;zI]{\ O?F***R$tJ- 54j[Vֈ] ]q"FVcgZq967i`ql*9`ҳ ^^y?o6ͺf;_ N;}4:qtc;:^K=^@w"]^yOÇI'{Gk֬wܡ.ApJ\|^4n`Eo?]DߟF ] qw0O1EQ158ҥ' r~(ϋ}F&|mQ+c1m=НZA>G_kҧT]].$I^n}h6l[:BO~/B.mۘhҷb.8Bk6n9Sғyi5mek7yXg<+j#*f`@ =zQQQ$B4{i?~~t+Зxi*uk7$V^תD{rM\[xOo׬o[%mtGp5FmC aXǘԘ4pơØ^T\\u4xԾc/}]'MujjsVڼ[ߤ C jРjj\xJ2I6㯵Z{ 8ѧ?L3g1tplJ zWl٠iZ^e辥/nSuUj)b^תgb?Tv5{""`ȚF L[! 8`Ԅ&Fx4sHz0}:r`v\ wIc/ʹPB4Ļ7EUѪ {N^br}Mp{@j|*Ǡ u k@/9Cc7z .,qjwc_ӼS>2J(JLS4Ǘ8:q|X[_ܣyGUhHCH/3 8+l$k pސW Ⲷ}mQѫ4xoT8DёŁwc/u5+5T-{Y˵d]Vw=Sל3 0MUtv\?qdt6G֕%b TKj(1hrUGuXU6jSoӇ*Bxwyɟum7+ ꇼkӷ A~q7,I0@Adph駶?0BmdofFsF(H(pAHO46xL|WNa[/ܪAX0G;/~Z{N|L6ZÊaWZVϭoɬQѦ2`XY{\9zXkdw`|&2$dؘUo w3~s}?#E 7T[VV:UTTraSii@(XCs~QW|[HyJNYѠÛոf5Mzc-xG AI t2M?XIKGyUC`sQ^h̘kg̓7gzgIApGhTC ͛U^>*` DB7oRm(EQ9m۫9޾pý*8DTqG9Uk׮p KԢիWs{8cjjj4rH7:qڌE[4#_[1GN$M~}ۤ/oRyt 2\;ҢQhB瓤D6IDAT*M|a%>t\VT pz~͚@* <X8=]`2P <*:5.<z?~[֓~r+WnBu 7ו&Tw,~+ j 3gW۷+NÆ ԩӸx75~K-}4x29tuAji˥h$kKdcrk$I_=FM.okr,WTq"2ɪon'B8DBF;1&$_%xm$+|wOx=qMB_$Tkrz]8(Tí P ŋEIyy+o4ƍhwpbd*m1eoE޻Hrw{ۯk\f*[y(M NY{GZ !dpہ-/>`{\mݺ%o +++Ze!~k~͝6r+/S Ǒ9=0svE/,` v3C`_@V,%?n0{Sa?>W2ܻsm ~ 8y0P z0`"WK=PvX> a&j8ЧUFub͝(b,胂o gAv7}_ֺZ=_^*)Sxu > 6ǘ_osk4 Voٯ,ުo5J Gښx8 Cu> Y[]Xh``p00A 0 !E8A 0]s5z!`{o| 8DC0! ~|H_)W=z5>W£KqGŚ}f#y*|1 ~U<Wܵ4U =JkiJ5Ir ͥE ,Q.?tƋR35S'mg/(֧;2˞ԋuyEK~_S!GѮ]{n޽[[nMmo۶-u;O:U ,Ќ3x@?D0Ogcljj5_iu %^%&jΜ\,ZZZ,IjmmUKKKj? `rsW[=;Q,ʗ|O?OF0W YoqT0oEtąѴܥsv0RLx~y^/fĸR~@?aw>όҔS/rGuɌ U9M:;BM[qnxf>zɱ"2uT-ZH7x#/0P #Z}u/lG/>]|ieSZx?0*~t'L 3:y٣wYp8]IWsSdp5"ӧk…>}z} &0н~qCӡM[UTt}/"zh;na#2+8|:9V$V'2Ve5@ޱ1 'M$J`omڳ :H;8ȰNaB f쀃QUUUiTmymٯ!e﮶Ǎ&Ed%t6g$7!u12Xԉzm[_UŹ >mHYT%QM1_#|`ƒ" F@ T5f2[%':kdGFd-Y+MIƏ&رcOU`dWaw2Et^X7)%\ @?hDI79P@.親5L 5&O&ǍF~epƓ@pھ۟ Vnw{k1cQXѤ1&/u] b@z1$#J8&gpnlR`.]aÕ@xDi@pXR2T8x"a#:ܚrF 19647h th{ddSV6yM82F:s?Z#Z}Y۩~Nzɼ*ݖMq2? V8Xzozޚn7egACוumv Ba?H֋CozÒpe"L%A7p;9nӈ488+o26 9+ȑ?)ʕ#9wu$keLz!Y&7Z9+fT+|S2?Gt\{3@'MMZI{K [mTN>*":|^ ;oLN9;L*5IT&RkfofMh.Y,c%v%II6~@^aɵ3[By`m9W_6] 38/ ?+vN7]lb%iRI}̨α6pտ 'Wld9#k]:/YkZ@r I3@097rT&+3B_ ɩ]"@d Fiv5dmA>C;ҕ~[̐7;UtiQsX'N}m`=Y &s] 6F t6tp ~^Kk&[@c3%[@>K:<#zW2UF TP  UBpvT pjSBPAoI:Z_K/@2fsUl_f<贬 |!pj}[Xzm'Hd\l@ A Upg_+EwXF,~¹`3c=9`hC?p#g],C`)8'Mz-,o]k9sp8xy+ ? ~uo* 'JYS$}B06&󾶂<@_K:o, G6uUf}i\k\yOH t;Rmyap(-fYO2mwZ[ ,P,?/o2Ǵq@vR08t=?Sю> 8a׮08nW:=#z0n_\}yѮ:Q[|}K'> ? WYIENDB`GSConnect-gnome-shell-extension-gsconnect-ea89821/data/metainfo/meson.build000066400000000000000000000010601477177637400270040ustar00rootroot00000000000000# 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.in000066400000000000000000000152011477177637400364360ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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/v62 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v61 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v60 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v59 https://github.com/GSConnect/gnome-shell-extension-gsconnect/releases/tag/v58 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.in000066400000000000000000000005361477177637400361510ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.in000066400000000000000000000006611477177637400337100ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.xml000066400000000000000000000117361477177637400344340ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.xml000066400000000000000000000137411477177637400340430ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/data/org.gnome.Shell.Extensions.GSConnect.sdp.xml000066400000000000000000000023661477177637400333020ustar00rootroot00000000000000 org.gnome.Shell.Extensions.GSConnect.service.in000066400000000000000000000003361477177637400336760ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/data/org.gnome.Shell.Extensions.GSConnect.xml000066400000000000000000000054171477177637400325150ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/000077500000000000000000000000001477177637400234605ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/connect-dialog.ui000066400000000000000000000122611477177637400267070ustar00rootroot00000000000000 1716 1764 1 1 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/contact-chooser.ui000066400000000000000000000121521477177637400271130ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/contacts-address-row.ui000066400000000000000000000065011477177637400300670ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/legacy-messaging-dialog.ui000066400000000000000000000212611477177637400304750ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/messaging-conversation-message.ui000066400000000000000000000062301477177637400321270ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/messaging-conversation-summary.ui000066400000000000000000000063531477177637400322060ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/messaging-conversation.ui000066400000000000000000000116741477177637400305150ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/messaging-window.ui000066400000000000000000000332011477177637400273000ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/mousepad-input-dialog.ui000066400000000000000000000267461477177637400302450ustar00rootroot00000000000000 touchpad-eventbox touchpad-eventbox 0 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/notification-reply-dialog.ui000066400000000000000000000171301477177637400310750ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/preferences-command-editor.ui000066400000000000000000000170261477177637400312260ustar00rootroot00000000000000 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-ea89821/data/ui/preferences-device-panel.ui000066400000000000000000004723321477177637400306650ustar00rootroot00000000000000 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-ea89821/data/ui/preferences-section-row.ui000066400000000000000000000060661477177637400305770ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/preferences-shortcut-editor.ui000066400000000000000000000135501477177637400314610ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/preferences-window.ui000066400000000000000000001324541477177637400276360ustar00rootroot00000000000000 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
Add device by IP… 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-ea89821/data/ui/service-device-chooser.ui000066400000000000000000000136671477177637400303710ustar00rootroot00000000000000 GSConnect-gnome-shell-extension-gsconnect-ea89821/data/ui/service-error-dialog.ui000066400000000000000000000200401477177637400300370ustar00rootroot00000000000000 expander 0 True GSConnect-gnome-shell-extension-gsconnect-ea89821/data/webextension/000077500000000000000000000000001477177637400255555ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/webextension/meson.build000066400000000000000000000026511477177637400277230ustar00rootroot00000000000000# 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.in000066400000000000000000000004461477177637400374360ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.license000066400000000000000000000001651477177637400410550ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/webextensionSPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later org.gnome.shell.extensions.gsconnect.json.mozilla.in000066400000000000000000000004231477177637400376240ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.license000066400000000000000000000001651477177637400412500ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/data/webextensionSPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-ea89821/eslint.config.mjs000066400000000000000000000256221477177637400254160ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import globals from 'globals'; import js from '@eslint/js'; import stylisticJs from '@stylistic/eslint-plugin-js'; import jsdoc from 'eslint-plugin-jsdoc'; import {defineConfig, globalIgnores} from 'eslint/config'; export default defineConfig([ js.configs.recommended, jsdoc.configs['flat/recommended'], globalIgnores([ '**/*.js', '!src/**/*.js', '!installed-tests/**/*.js', '!webextension/**/*.js', 'webextension/js/browser-polyfill*', ]), { files: [ 'src/**/*.js', 'installed-tests/**/*.js', 'webextension/**/*.js', ], plugins: { '@stylistic/js': stylisticJs, jsdoc, }, languageOptions: { globals: { ...globals['shared-node-browser'], 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, }, ecmaVersion: 'latest', sourceType: 'module', }, rules: { '@stylistic/js/array-bracket-newline': ['error', 'consistent'], '@stylistic/js/array-bracket-spacing': ['error', 'never'], 'array-callback-return': 'error', '@stylistic/js/arrow-spacing': 'error', 'block-scoped-var': 'error', '@stylistic/js/block-spacing': 'error', '@stylistic/js/brace-style': 'error', '@stylistic/js/comma-dangle': ['error', { arrays: 'always-multiline', objects: 'always-multiline', functions: 'never', }], '@stylistic/js/comma-spacing': ['error', { before: false, after: true, }], '@stylistic/js/comma-style': ['error', 'last'], '@stylistic/js/computed-property-spacing': 'error', curly: ['error', 'multi-or-nest', 'consistent'], '@stylistic/js/dot-location': ['error', 'property'], '@stylistic/js/eol-last': 'error', eqeqeq: 'error', '@stylistic/js/func-call-spacing': 'error', 'func-name-matching': 'error', 'func-style': ['error', 'declaration', { allowArrowFunctions: true, }], 'grouped-accessor-pairs': ['error', 'getBeforeSet'], '@stylistic/js/indent': ['error', 4, { ignoredNodes: [ 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child', ], MemberExpression: 'off', SwitchCase: 1, }], '@stylistic/js/key-spacing': ['error', { beforeColon: false, afterColon: true, }], '@stylistic/js/keyword-spacing': ['error', { before: true, after: true, }], '@stylistic/js/linebreak-style': ['error', 'unix'], '@stylistic/js/lines-between-class-members': 'error', 'max-nested-callbacks': ['error', { max: 5, }], '@stylistic/js/max-statements-per-line': 'error', '@stylistic/js/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-object-constructor': '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-self-compare': 'error', 'no-shadow-restricted-names': 'error', '@stylistic/js/no-tabs': 'error', 'no-template-curly-in-string': 'error', 'no-throw-literal': 'error', '@stylistic/js/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', '@stylistic/js/no-whitespace-before-property': 'error', 'no-with': 'error', '@stylistic/js/nonblock-statement-body-position': ['error', 'below'], '@stylistic/js/object-curly-newline': ['error', { consistent: true, }], '@stylistic/js/object-curly-spacing': 'error', 'operator-assignment': 'error', '@stylistic/js/operator-linebreak': 'error', 'prefer-const': 'error', 'prefer-numeric-literals': 'error', 'prefer-promise-reject-errors': 'error', 'prefer-rest-params': 'error', 'prefer-spread': 'error', '@stylistic/js/quotes': ['error', 'single', { avoidEscape: true, }], 'require-await': 'error', '@stylistic/js/rest-spread-spacing': 'error', '@stylistic/js/semi': ['error', 'always'], '@stylistic/js/semi-spacing': ['error', { before: false, after: true, }], '@stylistic/js/semi-style': 'error', '@stylistic/js/space-before-blocks': 'error', '@stylistic/js/space-before-function-paren': ['error', { named: 'never', anonymous: 'always', asyncArrow: 'always', }], '@stylistic/js/space-in-parens': 'error', '@stylistic/js/space-infix-ops': ['error', { int32Hint: false, }], '@stylistic/js/space-unary-ops': 'error', '@stylistic/js/spaced-comment': 'error', '@stylistic/js/switch-colon-spacing': 'error', 'symbol-description': 'error', '@stylistic/js/template-curly-spacing': 'error', '@stylistic/js/template-tag-spacing': 'error', 'unicode-bom': 'error', '@stylistic/js/wrap-iife': ['error', 'inside'], '@stylistic/js/yield-star-spacing': 'error', yoda: 'error', 'jsdoc/tag-lines': ['error', 'any', {'startLines': 1}], }, }, { files: ['webextension/js/*.js'], languageOptions: { globals: { ...globals.browser, ...globals.webextensions, }, }, rules: { 'no-console': ['error', { allow: ['warn', 'error'], }], }, }, { files: ['installed-tests/**/*.js'], languageOptions: { globals: { ...globals.jasmine, ...globals['shared-node-browser'], clearInterval: 'writable', clearTimeout: 'writable', setInterval: 'writable', setTimeout: 'writable', }, }, 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', }], }, }, ]); GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/000077500000000000000000000000001477177637400252515ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/config.js.in000066400000000000000000000002741477177637400274640ustar00rootroot00000000000000// 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-ea89821/installed-tests/data/000077500000000000000000000000001477177637400261625ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/data/album.png000066400000000000000000000015621477177637400277740ustar00rootroot00000000000000PNG  IHDR{C pHYs.#.#x?vtIME PKIDATxàS_UoPIENDB`GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/data/local-certificate.pem000066400000000000000000000014261477177637400322420ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICGTCCAb+gAwIBAgIUVw9/gMZ6nAXyg01TnEvA1b/+MZ0wCgYIKoZIzj0EAwIw YjEdMBsGA1UECgwUYW5keWhvbG1lcy5naXRodWIuaW8xEjAQBgNVBAsMCUdTQ29u bmVjdDEtMCsGA1UEAwwkNzI3ZjFlNjZfOGNjYV80ZmJjXzhjZWZfYmI1ZjhhNmQ3 NmE4MB4XDTI1MDEyMjIzNDA0NloXDTM1MDEyMDIzNDA0NlowYjEdMBsGA1UECgwU YW5keWhvbG1lcy5naXRodWIuaW8xEjAQBgNVBAsMCUdTQ29ubmVjdDEtMCsGA1UE AwwkNzI3ZjFlNjZfOGNjYV80ZmJjXzhjZWZfYmI1ZjhhNmQ3NmE4MFkwEwYHKoZI zj0CAQYIKoZIzj0DAQcDQgAEmfrGvWuO+B7HYMVLpwPwvj37tMI56M+mGD3vJqtC czlbUc93QWInx321DdiTvkSemZMVIiaNcLUTIPVhIBcXXKNTMFEwHQYDVR0OBBYE FOopjpkIVg+dDG8I61h+rGOXXUxiMB8GA1UdIwQYMBaAFOopjpkIVg+dDG8I61h+ rGOXXUxiMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpSmOMp NFzHPtcZ5EA8UGRe6dM1qtM/rDpWl60gX1W5AiAT5yHnLF3y5SQmDrI4DRJZ3uEj 5lAze4JDN3O3SZNWxA== -----END CERTIFICATE----- GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/data/local-private.pem000066400000000000000000000003611477177637400314270ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyI4fgKyLmxTgZpQG 3ReQ8ipNctiJP2+NW+7PQEJGWTahRANCAASZ+sa9a474HsdgxUunA/C+Pfu0wjno z6YYPe8mq0JzOVtRz3dBYifHfbUN2JO+RJ6ZkxUiJo1wtRMg9WEgFxdc -----END PRIVATE KEY----- GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/data/remote-certificate.pem000066400000000000000000000014261477177637400324430ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICGTCCAb+gAwIBAgIUegLNGL0VV3bIGpnKNNn6yOH+oHEwCgYIKoZIzj0EAwIw YjEdMBsGA1UECgwUYW5keWhvbG1lcy5naXRodWIuaW8xEjAQBgNVBAsMCUdTQ29u bmVjdDEtMCsGA1UEAwwkOTZjYTFmY2NfNzUyMV80MjI4X2FiYTVfN2M0MGQ0ODc5 ODA0MB4XDTI1MDEyMjIzNDI1MloXDTM1MDEyMDIzNDI1MlowYjEdMBsGA1UECgwU YW5keWhvbG1lcy5naXRodWIuaW8xEjAQBgNVBAsMCUdTQ29ubmVjdDEtMCsGA1UE AwwkOTZjYTFmY2NfNzUyMV80MjI4X2FiYTVfN2M0MGQ0ODc5ODA0MFkwEwYHKoZI zj0CAQYIKoZIzj0DAQcDQgAEqouOOaOeXSXTyLkzQ2bdZZ+XCTa0PO/EnRL3PSFK iMEDn5o8rZN2hvTpkBRTHlQcF97mMJ7K+h7eBVoz8tSgM6NTMFEwHQYDVR0OBBYE FAyHzYDRVOR9uh9aQBd7EVrWeMWnMB8GA1UdIwQYMBaAFAyHzYDRVOR9uh9aQBd7 EVrWeMWnMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAOQuvcDt CPqkhcAJkqn3DqIjDeEf3SJwyOFDszlC8bxpAiAqLfHPlk5bnhwtboWxZ9sC+Jpq 5QpPPhfQGmk6lfIzuw== -----END CERTIFICATE----- GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/data/remote-private.pem000066400000000000000000000003611477177637400316300ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfRzQEA8iVvpaFKiU MfVMeNbQN0T5l6QGx3SHvrC1ZVChRANCAASqi445o55dJdPIuTNDZt1ln5cJNrQ8 78SdEvc9IUqIwQOfmjytk3aG9OmQFFMeVBwX3uYwnsr6Ht4FWjPy1KAz -----END PRIVATE KEY----- GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/data/vcard-invalid.vcf000066400000000000000000000001661477177637400314100ustar00rootroot00000000000000BEGIN: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-ea89821/installed-tests/data/vcard-valid.vcf000066400000000000000000000203151477177637400310570ustar00rootroot00000000000000BEGIN: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-ea89821/installed-tests/fixtures/000077500000000000000000000000001477177637400271225ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/fixtures/backend.js000066400000000000000000000371501477177637400310550ustar00rootroot00000000000000// 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 { 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 { 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 { 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: 8, 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-ea89821/installed-tests/fixtures/components/000077500000000000000000000000001477177637400313075ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/fixtures/components/clipboard.js000066400000000000000000000014761477177637400336140ustar00rootroot00000000000000// 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-ea89821/installed-tests/fixtures/components/index.js000066400000000000000000000007151477177637400327570ustar00rootroot00000000000000// 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-ea89821/installed-tests/fixtures/components/input.js000066400000000000000000000005101477177637400330000ustar00rootroot00000000000000// 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-ea89821/installed-tests/fixtures/components/mpris.js000066400000000000000000000104131477177637400327760ustar00rootroot00000000000000// 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.js000066400000000000000000000012371477177637400342570ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/installed-tests/fixtures/components/pulseaudio.js000066400000000000000000000115431477177637400340230ustar00rootroot00000000000000// 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-ea89821/installed-tests/fixtures/components/session.js000066400000000000000000000015051477177637400333310ustar00rootroot00000000000000// 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-ea89821/installed-tests/fixtures/components/sound.js000066400000000000000000000034601477177637400330000ustar00rootroot00000000000000// 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-ea89821/installed-tests/fixtures/components/upower.js000066400000000000000000000021531477177637400331670ustar00rootroot00000000000000// 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-ea89821/installed-tests/fixtures/mpris.js000066400000000000000000000061331477177637400306150ustar00rootroot00000000000000// 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-ea89821/installed-tests/fixtures/utils.js000066400000000000000000000261041477177637400306230ustar00rootroot00000000000000// 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 */ /** * Find the path to the datadir via the import path. * * @returns {string} The absolute datadir path */ 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(); /** * Convert a filename into a path within the datadir * * @param {string} filename - A filename * @returns {string} An absolute path */ export function getDataPath(filename) { return GLib.build_filenamev([DATA_PATH, filename]); } /** * Convert a filename into a URI within the datadir * * @param {string} filename - A filename * @returns {string} An absolute-path URI */ export function getDataUri(filename) { return `file://${getDataPath(filename)}`; } /** * Create a Gio.File object for a filename within the datadir * * @param {string} filename - A filename * @returns {Gio.File} A File object representing the named file */ export function getDataFile(filename) { return Gio.File.new_for_path(getDataPath(filename)); } /** * Read the contents of a file * * @param {string} filename - The file to load * @returns {string} The file's contents as a UTF-8 string */ 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 */ /** * Get a pseudo-random device type * * @returns {string} A GSConnect device type */ 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 * @returns {object} A pseudo-random identity packet */ export function generateIdentity(params = {}) { const identity = { 'id': Date.now(), 'type': 'kdeconnect.identity', 'body': { 'deviceId': Device.generateId(), 'deviceName': 'Test Device', 'deviceType': getDeviceType(), 'protocolVersion': 8, '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 * @returns {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. * * @returns {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 { } file.delete(null); } catch { } } /** * 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 * @returns {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-ea89821/installed-tests/jasmine.js000066400000000000000000007626701477177637400272570ustar00rootroot00000000000000/* eslint-disable */ // SPDX-FileCopyrightText: Copyright (c) 2008-2020 Pivotal Labs // // SPDX-License-Identifier: MPL-2.0 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-ea89821/installed-tests/jasmine.test.in000066400000000000000000000001241477177637400302020ustar00rootroot00000000000000[Test] Type=session Exec=@jasmine_path@ @installed_tests_execdir@/@name@ Output=TAP GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/jasmine.test.in.license000066400000000000000000000001651477177637400316300ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/meson.build000066400000000000000000000036121477177637400274150ustar00rootroot00000000000000# 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-ea89821/installed-tests/minijasmine000077500000000000000000000075771477177637400275220ustar00rootroot00000000000000#!/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-ea89821/installed-tests/prepare-tests.sh000077500000000000000000000015051477177637400304070ustar00rootroot00000000000000#!/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-ea89821/installed-tests/suites/000077500000000000000000000000001477177637400265655ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/suites/backends/000077500000000000000000000000001477177637400303375ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/suites/backends/meson.build000066400000000000000000000022221477177637400324770ustar00rootroot00000000000000# 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-ea89821/installed-tests/suites/backends/testLanBackend.js000066400000000000000000000110451477177637400335600ustar00rootroot00000000000000// 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({ 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({ 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-ea89821/installed-tests/suites/components/000077500000000000000000000000001477177637400307525ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/suites/components/meson.build000066400000000000000000000025601477177637400331170ustar00rootroot00000000000000# 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.js000066400000000000000000000027221477177637400357160ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.js000066400000000000000000000075171477177637400351200ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/installed-tests/suites/core/000077500000000000000000000000001477177637400275155ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/suites/core/meson.build000066400000000000000000000022171477177637400316610ustar00rootroot00000000000000# 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-ea89821/installed-tests/suites/core/testDevice.js000066400000000000000000000063071477177637400321600ustar00rootroot00000000000000// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gio from 'gi://Gio'; 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).toBe(''); 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 = Device.generateId(); 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).toBe(''); 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-ea89821/installed-tests/suites/core/testManager.js000066400000000000000000000050661477177637400323340ustar00rootroot00000000000000// 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-ea89821/installed-tests/suites/core/testPacket.js000066400000000000000000000063621477177637400321710ustar00rootroot00000000000000// 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-ea89821/installed-tests/suites/core/testPlugin.js000066400000000000000000000165651477177637400322260ustar00rootroot00000000000000// 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-ea89821/installed-tests/suites/meson.build000066400000000000000000000003051477177637400307250ustar00rootroot00000000000000# 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-ea89821/installed-tests/suites/plugins/000077500000000000000000000000001477177637400302465ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/installed-tests/suites/plugins/meson.build000066400000000000000000000026371477177637400324200ustar00rootroot00000000000000# 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.js000066400000000000000000000155151477177637400342250ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.js000066400000000000000000000102751477177637400345100ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.js000066400000000000000000000110251477177637400343610ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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, }, }, }; /** * Mocked packet handling for the test device * * @param {*} packet - a KDE Connect protocol packet */ 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.js000066400000000000000000000046271477177637400350750ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.js000066400000000000000000000237251477177637400343720ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/installed-tests/suites/plugins/testMprisPlugin.js000066400000000000000000000234601477177637400337620ustar00rootroot00000000000000// 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.js000066400000000000000000000234661477177637400352450ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/installed-tests/suites/plugins/testPingPlugin.js000066400000000000000000000041301477177637400335560ustar00rootroot00000000000000// 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.js000066400000000000000000000037241477177637400345610ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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.js000066400000000000000000000071231477177637400347120ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/installed-tests/suites/plugins/testSftpPlugin.js000066400000000000000000000066301477177637400336040ustar00rootroot00000000000000// 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', }, }, }; /** * Mocked packet handling for the test device * * @param {*} packet - a KDE Connect protocol packet */ 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-ea89821/installed-tests/suites/plugins/testSharePlugin.js000066400000000000000000000070311477177637400337260ustar00rootroot00000000000000// 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-ea89821/installed-tests/suites/plugins/testSmsPlugin.js000066400000000000000000000207761477177637400334410ustar00rootroot00000000000000// 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, }, }, }; /** * Mocked packet handling for the test device * * @param {*} packet - a KDE Connect protocol packet */ 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.js000066400000000000000000000066361477177637400353330ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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'; /** * Mocked packet handling for the test device * * @param {*} packet - a KDE Connect protocol packet */ 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); testRig.setConnected(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 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.js000066400000000000000000000240051477177637400345540ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/meson.build000066400000000000000000000075641477177637400243100ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later project('gsconnect', 'c', version: '62', 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-ea89821/meson_options.txt000066400000000000000000000043531477177637400255740ustar00rootroot00000000000000# 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-ea89821/nautilus-extension/000077500000000000000000000000001477177637400260105ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/nautilus-extension/meson.build000066400000000000000000000007311477177637400301530ustar00rootroot00000000000000# 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-ea89821/nautilus-extension/nautilus-gsconnect.py000066400000000000000000000150531477177637400322130ustar00rootroot00000000000000# 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-ea89821/package.json000066400000000000000000000003141477177637400244160ustar00rootroot00000000000000{ "devDependencies": { "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", "@stylistic/eslint-plugin-js": "^3.0.0", "eslint-plugin-jsdoc": "^50.6.3", "globals": "^15.14.0" } } GSConnect-gnome-shell-extension-gsconnect-ea89821/package.json.license000066400000000000000000000001651477177637400260430ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-ea89821/po/000077500000000000000000000000001477177637400225505ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/po/LINGUAS000066400000000000000000000001731477177637400235760ustar00rootroot00000000000000ar be bg 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-ea89821/po/LINGUAS.license000066400000000000000000000001651477177637400252200ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-ea89821/po/POTFILES000066400000000000000000000030711477177637400237210ustar00rootroot00000000000000data/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/init.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-ea89821/po/POTFILES.license000066400000000000000000000001651477177637400253430ustar00rootroot00000000000000SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect SPDX-License-Identifier: GPL-2.0-or-later GSConnect-gnome-shell-extension-gsconnect-ea89821/po/ar.po000066400000000000000000001131371477177637400235200ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:05\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: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 لجنوم" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:24 msgid "GSConnect Team" msgstr "فريق GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "مشاركة الملفات، الروابط والنص" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "إرسال واستقبال الرسائل" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "مزامنة محتوى الحافظة" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "مزامنة جهات الاتصال" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "مزامنة الإشعارات" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "التحكم في مشغلات الوسائط" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "التحكم في مستوى صوت النظام" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "تنفيذ أوامر محددة مسبقاً" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "والمزيد…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect في صدفة غنوم" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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 "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: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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "الإشعارات" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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 "توصيل آلي لـ FTP المحمي" #: 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:367 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:390 msgid "Encryption Info" msgstr "معلومات التشفير" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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: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 يبقى نشطاً عند قفل جنوم شل" #: 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:144 msgid "Discovery Disabled" msgstr "اكتشاف معطل" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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 متصل" msgstr[2] "%d متصل" msgstr[3] "%d متصل" msgstr[4] "%d متصل" msgstr[5] "%d متصل" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "تعديل" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "إزالة" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "معطّل" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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 كامل لربط 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:175 msgid "Click for help troubleshooting" msgstr "انقر للمساعدة في استكشاف الأخطاء" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "انقر للحصول على مزيد من المعلومات" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "رقم الطلب" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "مشاركة الملف" #: src/service/daemon.js:337 msgid "List available devices" msgstr "قائمة الأجهزة المتاحة" #: src/service/daemon.js:346 msgid "List all devices" msgstr "قائمة جميع الأجهزة" #: src/service/daemon.js:355 msgid "Target Device" msgstr "الجهاز المستهدف" #: src/service/daemon.js:397 msgid "Message Body" msgstr "نص الرسالة" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "إرسال إشعار" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "اسم تطبيق الإشعارات" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "نص الإشعار" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "أيقونة الإشعار" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "معرف الإشعار" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "بينغ" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "رنين" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "مشاركة الرابط" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "مشاركة النص" #: src/service/daemon.js:505 msgid "Show release version" msgstr "إظهار رقم الإصدار" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "جهاز البلوتوث في %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "مفتاح التحقق: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "طلب الاقتران من %s" #: src/service/device.js:853 msgid "Reject" msgstr "رفض" #: src/service/device.js:858 msgid "Accept" msgstr "قبول" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "تم تعطيل الاكتشاف بسبب عدد الأجهزة على هذه الشبكة." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "لم يتم العثور على OpenSSL" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "المنفذ قيد الاستخدام بالفعل" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "تبادل معلومات البطارية" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "مشحونة بالكامل" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% مشحون" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: البطارية منخفضة" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% متبقي" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "الحافظة" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "مشاركة محتوى الحافظة" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "دفع الحافظة" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "سحب الحافظة" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "الوصول إلى جهات الاتصال من الجهاز المقترن" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "العثور على هاتفي" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "شغّل رنين جهازك المقترن" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "لوحة الفأرة" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "تمكين الجهاز المقترن للعمل كفأرة عن بعد ولوحة المفاتيح" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "الإدخال عن بعد" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS مواصفات واجهة مشغل الوسائط عن بعد" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "التحكم في تشغيل الوسائط عن بعد ثنائي الاتجاه" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "غير معروف" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "مشاركة الإشعارات مع الجهاز المقترن" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "إلغاء الإشعارات" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "إغلاق الإشعارات" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "الرد على الإشعارات" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "تفعيل الإشعارات" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "الوخز: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "عرض" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "استخدم الجهاز المقترن كمقدم عرض" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "تشغيل الأوامر" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "تشغيل الأوامر على جهازك المقترن أو السماح للجهاز بتشغيل الأوامر المحددة مسبقاً على هذا الكمبيوتر" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "اتصال FTP محمى" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "تصفح نظام ملفات الجهاز المقترن" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "صِل" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "افصل" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s التبليغ عن خطأ" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "شارك" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "مشاركة الملفات والروابط بين الأجهزة" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "فشل التحويل" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s غير مسموح له برفع الملفات" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "نقل الملف" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "تلقي \"%s\" من %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "تم التحويل بنجاح" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "تلقى \"%s\" من %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "إظهار موقع الملف" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "فتح ملف" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "فشل تلقي \"%s\" من %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "نص مشترك من قبل %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "إرسال \"%s\" إلى %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "فشل إرسال \"%s\" إلى %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "إرسال ملفات إلى %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "فتح عند الانتهاء" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "إرسال رابط إلى %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "الرسائل القصيرة" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "إرسال وقراءة الرسائل القصيرة للجهاز المقترن مع إعلامك بالرسائل القصيرة الجديدة" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "رسالة نصية جديدة (الرابط)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "الرد كرسالة" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "مشاركة الرسائل القصيرة" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "مستوى صوت النظام" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "تمكين الجهاز المقترن للتحكم في مستوى صوت النظام" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "لم يتم العثور على PulseAudio" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "كتم المكالمة" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "مكالمة واردة" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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 دقيقة" msgstr[2] "%d دقائق" msgstr[3] "%d دقائق" msgstr[4] "%d دقائق" msgstr[5] "%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 جهة اتصال أخرى" 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:115 #, 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، مباشرة إلى المتصفح أو بواسطة الرسائل القصيرة." #. 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-ea89821/po/be.po000066400000000000000000001150461477177637400235050ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:05\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: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:24 msgid "GSConnect Team" msgstr "Каманда GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Абагульваць файлы, спасылкі і тэкст" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Адпраўляць і прымаць паведамленні" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Сінхранізаваць змесціва буфера абмену" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Сінхранізаваць кантакты" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Сінхранізаваць апавяшчэнні" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Кіраваць медыяпрайгравальнікамі" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Кіраваць гучнасцю сістэмы" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Выконваць папярэдне зададзеныя каманды" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "І іншае…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect у GNOME Shell" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "Апавяшчэнні" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "Інфармацыя пра шыфраванне" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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: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:144 msgid "Discovery Disabled" msgstr "Знаходжанне адключана" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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 падлучаны" msgstr[2] "%d падлучана" msgstr[3] "%d падлучана" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Рэдагаваць" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Выдаліць" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Адключана" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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 "Maksim Krapiŭka " #: 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:175 msgid "Click for help troubleshooting" msgstr "Націсніце для дапамогі ў выпраўленні непаладак" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Націсніце для большай інфармацыі" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Набраць нумар" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Абагуліць файл" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Спіс даступных прылад" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Спіс усіх прылад" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Мэтавая прылада" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Змест паведамлення" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Адправіць апавяшчэнне" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Назва праграмы ў апавяшчэнні" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Змест апавяшчэння" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Значок апавяшчэння" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Ідэнтыфікатар апавяшчэння" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Пінг" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Званок" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Абагуліць спасылку" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Абагуліць тэкст" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Паказаць версію праграмы" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Запыт спалучэння ад %s" #: src/service/device.js:853 msgid "Reject" msgstr "Адхіліць" #: src/service/device.js:858 msgid "Accept" msgstr "Прыняць" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Знаходжанне было адключана праз колькасць прылад у гэтай сетцы." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "OpenSSL не знойдзены" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Порт ужо выкарыстоўваецца" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Абмен інфармацыяй пра акумулятар" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Зараджана цалкам" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% зараджана" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: нізкі зарад акумулятара" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% засталося" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Буфер абмену" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Абагульваць змесціва буфера абмену" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Адправіць буфер абмену" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Запрасіць буфер абмену" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Доступ да кантактаў спалучанай прылады" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Адшукаць мой тэлефон" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Адгукацца спалучанай прыладай" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Тачпад" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Дазваляе спалучанай прыладзе дзейнічаць як аддаленай мышшу і клавіятурай" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Двухнакіраванае аддаленае кіраванне прайграваннем" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Невядомы" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Абагульваць апавяшчэнні са спалучанымі прыладамі" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Скасаваць апавяшчэнне" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Закрыць апавяшчэнне" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Адказаць на апавяшчэнне" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Актываваць апавяшчэнне" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Пінг: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Прэзентацыя" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Выкарыстоўваць спалучаную прыладу для прэзентацыі" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Запускаць каманды" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Запускаць каманды на спалучанай прыладзе або дазволіць ёй запускаць прадвызначаныя каманды на гэтым ПК" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Праглядаць файлавую сістэму спалучанай прылады" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Падлучыць" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Адлучыць" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s паведаміў пра памылку" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Абагуліць" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Абагульваць файлы і спасылкі паміж прыладамі" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Збой перадачы" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s не дазваляе запампоўваць файлы" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Перадача файла" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Атрыманне “%s” з %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Паспяхова перададзена" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Атрымана “%s” з %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Адкрыць файл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Не ўдалося атрымаць “%s” з %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "Тэкст абагулены %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "Адпраўка “%s” у %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Збой адпраўкі “%s” у %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "Адправіць файлы ў %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "Адкрыць па сканчэнні" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Адправіць спасылку ў %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Адпраўляць і чытаць SMS на спалучанай прыладзе, апавяшчаць пра новыя SMS" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Новае SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Адказаць на SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Абагуліць SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Сістэмная гучнасць" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Дазволіць спалучанай прыладзе кіраваць гучнасцю сістэмы" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio не знойдзены" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Адкл. гук выкліку" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "Уваходны выклік" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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 хвіліны" msgstr[2] "%d хвілін" 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] "І яшчэ %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:115 #, 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-ea89821/po/bg.po000066400000000000000000001201071477177637400235010ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-26 12:40\n" "Last-Translator: \n" "Language-Team: Bulgarian\n" "Language: bg_BG\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Project-ID: 327933\n" "X-Crowdin-Language: bg\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:24 msgid "GSConnect Team" msgstr "Екип на GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Споделяйте файлове, връзки и текст" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Изпращане и получаване на съобщения" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Синхронизиране на съдържанието на клипборда" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Синхронизиране на контакти" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Синхронизиране на известията" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Контролирайте медийни плейъри" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Контролирайте силата на звука на системата" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Изпълнение на предварително дефинирани команди" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "И още…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect в GNOME Shell" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "Известия" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "Информация за криптиране" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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 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:144 msgid "Discovery Disabled" msgstr "Откриването е деактивирано" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "Добавяне на устройство по IP…" #: 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:669 src/preferences/device.js:675 msgid "Edit" msgstr "Редактиране" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Премахнете" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Забранено" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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 "trunars" #: 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:175 msgid "Click for help troubleshooting" msgstr "Кликнете за помощ при отстраняване на неизправности" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Кликнете за повече информация" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Набиране на номер" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Споделяне на файл" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Избройте наличните устройства" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Избройте всички устройства" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Целево устройство" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Съдържание на съобщението" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Изпращане на известие" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Име на приложение за известие" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Съдържание за известие" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Икона за известие" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Идент. № на известие" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Пинг" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Звънене" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Споделете връзка" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Споделяне на текст" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Показване на версията на изданието" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Ключ за потвърждение: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Заявка за сдвояване от %s" #: src/service/device.js:853 msgid "Reject" msgstr "Отхвърли" #: src/service/device.js:858 msgid "Accept" msgstr "Приеми" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Откриването е деактивирано поради броя на устройствата в тази мрежа." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "OpenSSL не е намерен" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Портът вече се използва" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Обменете информация за батерията" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Напълно заредена" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Заредено" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Батерията е изтощена" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% остават" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Клипборд" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Споделете съдържанието на клипборда" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Натискане на клипборда" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Издърпване на клипборда" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Достъп до контактите на сдвоеното устройство" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Намерете моя телефон" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Позвънете на вашето сдвоено устройство" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Мауспад" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Позволява на сдвоеното устройство да действа като отдалечена мишка и клавиатура" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Дистанционен вход" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Двупосочно дистанционно управление на възпроизвеждането на мултимедия" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Непознато" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Споделете известия със сдвоеното устройство" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Отмяна на известие" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Затвори известие" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Известие за отговор" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Активирайте известие" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Пинг: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Представяне" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Използвайте сдвоеното устройство като водещ" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Изпълнение на команди" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Изпълнете команди на вашето сдвоено устройство или оставете устройството да изпълнява предварително зададени команди на този компютър" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Прегледайте файловата система на сдвоеното устройство" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Монтиране" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Демонтиране" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s съобщава за грешка" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Споделяне" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Споделяйте файлове и URL адреси между устройства" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Прехвърлянето е неуспешно" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s не е позволено да качва файлове" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Прехвърляне на файл" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Получаване “%s” от %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Прехвърлянето е успешно" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Получаване “%s” от %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Показване на местоположението на файла" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Отваряне на файл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Неуспешно получаване “%s” от %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "Текст, споделен от %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "Изпращане “%s” към %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Неуспешно изпращане “%s” до %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "Изпращане на файлове до %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "Отворете, когато сте готови" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Изпратете връзка към %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Изпращайте и четете SMS на сдвоеното устройство и получавайте известия за нов SMS" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Нов SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Отговор на SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Споделете SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Сила на звука на системата" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Активирайте сдвоеното устройство за контрол на силата на звука на системата" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio не е намерен" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Заглушаване на повикване" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "Входящо повикване" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 msgid "Ongoing call" msgstr "Текущо повикване" #. 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 "Работен" #. 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:115 #, 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-ea89821/po/ca.po000066400000000000000000001030561477177637400235000ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:05\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:30 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:24 msgid "GSConnect Team" msgstr "L’equip del GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Compartir fitxers, enllaços i text" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Enviar i rebre missatges" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Sincronitzar el contingut del porta-retalls" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Sincronitzar els contactes" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Sincronitzar les notificacions" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Controlar reproductors multimèdia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Controlar el volum del sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Executar ordres predefinides" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "I més…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect en el GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Envia el missatge" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Missatgeria" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Ordinador d’escriptori" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Sincronització del porta-retalls" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Reproductors multimèdia" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Ratolí i teclat" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Control de volum" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Fitxers" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Rebre fitxers" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Desa els fitxers a" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Compartició" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Bateria del dispositiu" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Notificació de bateria baixa" #: 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 "Notificació de bateria carregada" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Bateria del sistema" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Comparteix estadístiques" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Bateria" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Ordres" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Notificacions de compartició" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Comparteix quan estigui actiu" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Aplicacions" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notificacions" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contactes" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Trucades entrants" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volum" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Posa en pausa la multimèdia" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Trucades en curs" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Silencia el micròfon" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonia" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Dreceres d’accions" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Reinicialitza-ho tot…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Dreceres" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Connectors" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experiments" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Memòria cau del dispositiu" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Neteja la memòria cau…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Compatibilitat d’SMS llegat" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Muntatge automàtic d’SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avançat" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Dreceres de teclat" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Paràmetres del dispositiu" #. 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:367 msgid "Pair" msgstr "Aparella" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "No s’ha aparellat el dispositiu" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informació de xifratge" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Desaparella" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Al dispositiu" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Des del dispositiu" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Res" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Restaura" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Abaixa" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Descobriment deshabilitat" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Envia al %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 "Envia al dispositiu mòbil" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Sincronitza entre els teus dispositius" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d connectat" msgstr[1] "%d connectats" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Edita" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Elimina" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Desactivat" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s ja s’està fent servir" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Adolfo Jayme Barrientos , 2019-2020\n" "Marc Riera Irigoyen , 2019" #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Mostra el registre" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Ordinador portàtil" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Telèfon intel·ligent" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tauleta" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisió" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Desaparellat" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Desconnectat" #: src/preferences/service.js:510 msgid "Connected" msgstr "Connectat" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "S’està esperant el servei…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Ajuda per a resoldre el problema" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Més informació" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Comparteix un fitxer" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Enumera els dispositius disponibles" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Enumera tots els dispositius" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Dispositiu de destinació" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Cos del missatge" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Envia una notificació" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Nom d’aplicació de la notificació" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Corps de la notificació" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Icona de notificació" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Identificador de la notificació" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Comprovació de connectivitat" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Truca" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Comparteix un enllaç" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Comparteix text" #: src/service/daemon.js:505 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Clau de verificació: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Sol·licitud d’aparellament d’un %s" #: src/service/device.js:853 msgid "Reject" msgstr "Rebutja-la" #: src/service/device.js:858 msgid "Accept" msgstr "Accepta-la" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "No s’ha trobat l’OpenSSL" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Càrrega completa" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% restant" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Porta-retalls" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Troba el meu telèfon" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Desconegut" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Comprovació de connectivitat: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Presentació" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Execució d’ordres" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Munta" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Desmunta" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Compartició" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Ha fallat la transferència" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "S’està rebent «%s» del %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "La transferència ha estat exitosa" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "S’ha rebut «%s» del %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Mostra la ubicació del fitxer" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Obre el fitxer" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "No s’ha pogut rebre «%s» del %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "S’ha enviat «%s» al %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "No s’ha pogut enviar «%s» al %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Obre’l en finalitzar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Envia un enllaç al %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "SMS nou (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volum del sistema" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "No s’ha trobat el PulseAudio" #: src/service/plugins/telephony.js:16 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:28 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:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Contacte desconegut" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Trucada entrant" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Ara mateix" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Ahir・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minuts" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "No disponible" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Missatge de grup" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Vós: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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 "%d%% (en estimació…)" #. 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 "%d%% (%d∶%02d restants)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Respon" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "El servei no està disponible" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Obre al navegador" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/cs.po000066400000000000000000001066021477177637400235220ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "Kolektiv GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Sdílet soubory, odkazy a text" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Odesílat a přijímat zprávy" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Synchronizovat obsah schránky" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Synchronizovat kontakty" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Synchronizovat oznámení" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Ovládat přehrávání multimédií" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Ovládat hlasitost systému" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Spouštět předpřipravené příkazy" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "A další…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect v GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Odeslat zprávu" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 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:1268 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:482 msgid "Desktop" msgstr "Počítač" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Synchronizace schránky" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Přehrávače médií" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Myš a klávesnice" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Ovládání hlasitosti" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Soubory" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Přijmout soubory" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Uložit soubory do" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Sdílení" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Akumulátor zařízení" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Upozornění na téměř vybitý akumulátor" #: data/ui/preferences-device-panel.ui:649 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:729 msgid "Fully Charged Notification" msgstr "Oznámení o úplném dobití" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Systémový akumulátor" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Sdílet statistiky" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Akumulátor" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Příkazy" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Sdílet oznámení" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Sdílet když je aktivní" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Aplikace" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Oznámení" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontakty" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Příchozí hovory" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Hlasitost" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Pozastavit multimédia" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Probíhající hovory" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Ztlumit mikrofon" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonie" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Klávesové zkratky akcí" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Vrátit vše na výchozí hodnoty…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Klávesové zkratky" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Zásuvné moduly" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimentální" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Mezipaměť zařízení" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Vymazat mezipaměť…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Podpora původních SMS zpráv" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "SFTP automatické připojení" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Pokročilé" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Klávesové zkratky" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Nastavení zařízení" #. 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:367 msgid "Pair" msgstr "Spárování" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Zařízení není spárováno" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informace o šifrování" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Zrušit spárování" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Do zařízení" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Ze zařízení" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Nic" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Obnovit" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Snížit" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Objevování vypnuto" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Poslat na %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 "Odeslat do mobilního zařízení" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Synchronizace mezi zařízeními" #: src/extension.js:159 #, 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:669 src/preferences/device.js:675 msgid "Edit" msgstr "Upravit" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Odebrat" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Vypnuto" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s už je používáno" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Úplná (re)implementace KDE Connect pro GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Pavel Borecki " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Prohlédnout si záznam událostí" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Notebook" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Chytrý telefon" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Televize" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Nespárováno" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Odpojeno" #: src/preferences/service.js:510 msgid "Connected" msgstr "Připojeno" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Čeká se na službu…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Kliknutím otevřete nápovědu pro odstraňování potíží" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Další informace získáte kliknutím" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Vytočit číslo" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Sdílet soubor" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Vypsat zařízení k dispozici" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Vypsat všechna zařízení" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Cílové zařízení" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Tělo zprávy" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Poslat oznámení" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Název oznamovací aplikace" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Tělo oznámení" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Ikona oznámení" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Identif. oznámení" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Vyzvánění" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Sdílet odkaz" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Sdílet text" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Zobrazit verzi vydání" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Ověřovací klíč: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Žádost o spárování od %s" #: src/service/device.js:853 msgid "Reject" msgstr "Odmítnout" #: src/service/device.js:858 msgid "Accept" msgstr "Přijmout" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL nebylo nalezeno" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Port už je používán" #: src/service/plugins/battery.js:15 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:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Plně nabito" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% nabito" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Akumulátor je téměř vybitý" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% zbývá" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Schránka" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Sdílet obsah schránky" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Odesílání schránky" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Stahování schránky" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Přistupovat ke kontaktům na spárovaném zařízení" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Najít můj telefon" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Prozvonit vaše spárované zařízení" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Myš" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Vzdálený vstup" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 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:329 msgid "Unknown" msgstr "Neznámé" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Sdílet oznámení se spárovaným zařízením" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Zrušit oznámení" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Zavřít oznámení" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Odpovědět na oznámení" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Zapnout oznamování" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Prezentace" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Použít spárované zařízení jako prezentátor" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Spustit příkazy" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Procházet souborový systém spárovaného zařízení" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Připojit" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Odpojit" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s nahlásilo chybu" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Sdílet" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Sdílet soubory a URL adresy mezi zařízeními" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Přenos se nezdařil" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s není umožněno nahrávat soubory" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Přenášení souboru" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Získávání „%s“ z %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Přenos úspěšný" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Obdrženo „%s“ z %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Zobrazit umístění souboru" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Otevřít soubor" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Nepodařilo se přijmout „%s“ od %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "„%s“ odesláno do %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Nepodařilo se odeslat „%s“ do %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Po dokončení otevřít" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Poslat odkaz do %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Nová SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Odpovědět na SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Sdílet SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Hlasitost systému" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio nenalezeno" #: src/service/plugins/telephony.js:16 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:28 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:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Neznámý kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Příchozí hovor" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Právě teď" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Včera・%s" #: src/service/ui/messaging.js:151 #, 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:401 msgid "Not available" msgstr "Není k dispozici" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Skupinová zpráva" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Vy: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d zbývá)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Odpovědět" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Služba nedostupná" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Otevřít v prohlížeči" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/da.po000066400000000000000000001001401477177637400234700ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "GSConnect hold" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Send og modtag beskeder" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Synkroniser udklipsholder indhold" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Synkroniser kontakter" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Synkroniser notifikationer" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Og mere…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect i GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Send Meddelelse" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Messaging" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Stationær Computer" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Udklipsholder synkroniseres" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Medieafspillere" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Mus & Tastatur" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Lydstyring" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Filer" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Modtag Filer" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Gem filer til" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Deling" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Enhedens Batteri" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Lavt Batteri Notifikation" #: 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 "Fuldt Opladet Notifikation" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "System Batteri" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Del Statistikker" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Batteri" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Kommandoer" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Del Notifikationer" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Del Når Aktiv" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Applikationer" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notifikationer" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontakter" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Indgående Opkald" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Lydstryke" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Pause Media" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Igangværende Opkald" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Stum Mikrofon" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefoni" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Handlings Genveje" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Nulstil Alle…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Genveje" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Plugins" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Eksperimentel" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Enhed Cache" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Ryd Cache…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Arv SMS" #: 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 "Avanceret" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Tastaturgenveje" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Enhed Indstillinger" #. 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:367 msgid "Pair" msgstr "Par" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Enheden er uparret" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Kryptering Info" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Koble fra en enhed" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Til Apparat" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Fra Enhed" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Intet" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Gendan" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Reducere" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Søger efter enheder…" #: 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 "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:144 msgid "Discovery Disabled" msgstr "Discovery Deaktiveret" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Send til %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 "Send til Mobil Enhed" #: 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 Tilsuttet" msgstr[1] "%d Tilsuttet" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Rediger" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Fjerne" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Deaktiveret" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s er allerede bliver brugt" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "En komplet KDE Connect implementering for GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "oversætter-kreditter" #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Gennemgå Loggen" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Bærbar" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Fjernsyn" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Ikke parret" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Frakoblet" #: src/preferences/service.js:510 msgid "Connected" msgstr "Tilsluttet" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Venter på service…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Klik for hjælp fejlfinding" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Klik for mere information" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Opkaldsnummer" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Del Fil" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Liste over tilgængelige enheder" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Liste alle enheder" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Målet Enhed" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Beskedkrop" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Send Notifikation" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Notifikation Ikon" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Notifikation ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Ring" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Del Link" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Del tekst" #: src/service/daemon.js:505 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Paranmodning fra %s" #: src/service/device.js:853 msgid "Reject" msgstr "Afvise" #: src/service/device.js:858 msgid "Accept" msgstr "Acceptere" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL ikke fundet" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Fuldt Opladet" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Batteriet er lavt" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% tilbage" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Udklipsholder" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Udklipsholder Tryk" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Udklipsholder Trække" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Find min telefon" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Musemåtte" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Ukendt" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Annullere Besked" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Luk Besked" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Svar Notifikation" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Aktiver Notifikation" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Præsentation" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Udføre Kommando" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Monter" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Afmonter" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Del" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Overførsel mislykkedes" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s er ikke tilladt at uploade filer" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Overfører Fil" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Modtager \"%s\" fra %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Overførsel Succesfuld" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Modtaget \"%s\" fra %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Åben fil" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Kunne ikke modtage \"%s\" fra %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Sendt \"%s\" til %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Kunne ikke send \"%s\" til %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Åbne, når færdig" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Send et link til %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Ny SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Besvar SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Del SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Systemlydstyrke" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio ikke fundet" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Stum Opkald" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Ukendt Kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Indgående Opkald" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Lige Nu" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "I går・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minutter" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Ikke Tilgængelig" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Gruppebesked" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Du: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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 "%d%% (Estimering…)" #. 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 Indtil Fuld)" #. 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 Resterende)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Svar" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Tjenesten er ikke tilgængelig" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Åbn i browser" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/de.po000066400000000000000000001061371477177637400235100ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-02-24 23:19\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: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-Implementierung für GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:24 msgid "GSConnect Team" msgstr "GSConnect-Team" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Dateien, Links und Text teilen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Nachrichten senden und abrufen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Inhalt der Zwischenablage abgleichen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Kontakte synchronisieren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Benachrichtigungen synchronisieren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Medienwiedergaben steuern" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Systemlautstärke steuern" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Vordefinierte Befehle ausführen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Und mehr …" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect in der GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Nachricht senden" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Nachrichten" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Schreibtisch" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Zwischenablagesynchronisierung" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Medienwiedergaben" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Maus & Tastatur" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Lautstärkeregler" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Dateien" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Dateien empfangen" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Dateien speichern unter" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Teilen" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Geräteakku" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Benachrichtigung bei geringem Akkustand" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "»Benutzerdefinierter Ladestand erreicht«-Benachrichtigung" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "»Vollständig geladen«-Benachrichtigung" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Systemakku" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Statistik teilen" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Akku" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Befehle" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Benachrichtigungen freigeben" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Teilen wenn aktiv" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Anwendungen" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Benachrichtigungen" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontakte" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Eingehende Anrufe" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Lautstärke" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Medienwiedergabe pausieren" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Laufende Anrufe" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Mikrofon stummschalten" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonie" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Aktionstastenkürzel" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Alles zurücksetzen …" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Tastenkürzel" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Module" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimentell" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Gerätezwischenspeicher" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Zwischenspeicher leeren …" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Alte SMS-Unterstützung" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Automatisches SFTP-Einhängen" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Erweitert" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Tastenkürzel" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Geräteeinstellungen" #. 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:367 msgid "Pair" msgstr "Koppeln" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Gerät ist nicht gekoppelt" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Verschlüsselungsinfo" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Entkoppeln" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Zum Gerät" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Vom Gerät" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Nichts" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Wiederherstellen" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Leiser" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Erkennen deaktiviert" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "Gerät nach IP hinzufügen …" #: 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:402 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:38 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: 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 "An %s senden" #. TRANSLATORS: Top-level context menu item for GSConnect #: nautilus-extension/nautilus-gsconnect.py:195 webextension/gettext.js:34 msgid "Send To Mobile Device" msgstr "An Mobilgerät senden" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Geräte synchronisieren" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d verbunden" msgstr[1] "%d verbunden" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Bearbeiten" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Entfernen" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Deaktiviert" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s wird bereits verwendet" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "taaem \n" "Tobias Bannert \n" "Björn Daase (BjoernDaase)" #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Protokoll überprüfen" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Fernseher" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Ungekoppelt" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Getrennt" #: src/preferences/service.js:510 msgid "Connected" msgstr "Verbunden" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Auf Dienst wird gewartet …" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Für Hilfe bei der Fehlerbehebung hier klicken" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Für mehr Informationen klicken" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Nummer wählen" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Datei freigeben" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Alle verfügbaren Geräte anzeigen" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Alle Geräte anzeigen" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Zielgerät" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Nachrichtentext" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Benachrichtigung senden" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Name der Benachrichtigungs-App" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Benachrichtigungsinhalt" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Benachrichtigungssymbol" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Benachrichtigungs-ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Klingeln" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Link freigeben" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Text teilen" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Neuste Version zeigen" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Bestätigungsschlüssel: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Kopplung von %s angefordert" #: src/service/device.js:853 msgid "Reject" msgstr "Ablehnen" #: src/service/device.js:858 msgid "Accept" msgstr "Annehmen" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL nicht gefunden" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Port wird bereits verwendet" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Akku-Informationen austauschen" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Vollständig geladen" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% geladen" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Akku ist leer" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% verbleibend" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Zwischenablage" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Inhalt der Zwischenablage freigeben" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Zwischenablage senden" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Zwischenablage laden" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Auf Kontakte des verbundenen Gerätes zugreifen" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Mein Handy finden" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Das verbundene Gerät klingeln lassen" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mauspad" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Eingaben aus der Ferne" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Bidirektionale Steuerung der Medienwiedergabe" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Unbekannt" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Benachrichtigungen für das verbundene Gerät freigeben" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Benachrichtigung abbrechen" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Benachrichtigung schließen" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Auf Benachrichtigung antworten" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Benachrichtigung aktivieren" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Präsentation" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Das verbundene Gerät als Präsentations-Fernbedienung nutzen" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Befehle ausführen" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Das Dateisystem des verbundenen Gerätes durchsuchen" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Einhängen" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Aushängen" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s meldete einen Fehler" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Teilen" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Dateien und URLs zwischen Geräten freigeben" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Übertragung gescheitert" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s besitzt keine Berechtigung, Dateien hochzuladen" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Datei wird übertragen" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "»%s« wird von %s empfangen" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Übertragung erfolgreich" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "»%s« von %s empfangen" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Speicherort der Datei anzeigen" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Datei öffnen" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Empfang von »%s« von %s gescheitert" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "»%s« an %s gesendet" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Senden von »%s« an %s gescheitert" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Öffnen wenn abgeschlossen" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Verweis an %s senden" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Neue SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Auf SMS antworten" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "SMS freigeben" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Systemlautstärke" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio nicht gefunden" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Anruf stummschalten" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Unbekannter Kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Eingehender Anruf" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Gerade jetzt" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Gestern・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d Minute" msgstr[1] "%d Minuten" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Nicht verfügbar" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Gruppennachricht" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Sie: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d verbleibend)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Antworten" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Dienst nicht verfügbar" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Im Browser öffnen" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/el.po000066400000000000000000001224141477177637400235140ustar00rootroot00000000000000# 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: 2025-01-03 13:38-0500\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:24 msgid "GSConnect Team" msgstr "Η ομάδα του GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Μοιραστείτε αρχεία, συνδέσμους και κείμενο" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Στείλτε και λάβετε μηνύματα" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Συγχρονίσετε το περιεχόμενο του πρόχειρου σας" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Συγχρονίσετε της επαφές σας" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Συγχρονίσετε της ειδοποίησης σας" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Ελέγξετε της συσκευές σας αναπαραγωγής πολυμέσων" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Ελέξετε την ένταση του συστήματος σας" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Εκτελέστε προκαθορισμένες εντολές" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Και άλλα…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect στο γραφικό κέλυφος GNOME" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "Ειδοποιήσεις" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "Πληροφορίες κρυπτογράφησης" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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:144 msgid "Discovery Disabled" msgstr "Η ανακάλυψη είναι απενεργοποιημένη" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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:669 src/preferences/device.js:675 msgid "Edit" msgstr "Επεξεργασία" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Αφαίρεση" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Ανεσταλμένες" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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:175 msgid "Click for help troubleshooting" msgstr "Κάντε κλικ για την βοήθεια αντιμετώπισης προβλημάτων" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Κάντε κλικ για περισσότερες πληροφορίες" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Πληκτρολογήστε τον αριθμό" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Κοινή χρήση αρχείου" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Λίστα διαθέσιμων συσκευών" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Λίστα όλων των συσκευών" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Συσκευή προορισμού" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Κορμός μηνύματος" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Αποστολή ειδοποίησης" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Ονομασία εφαρμογής ειδοποίησης" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Κορμός ειδοποίησης" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Εικονίδιο ειδοποίησης" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Αναγνωριστικό ειδοποίησης" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Βρες" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Διαμοιρασμός συνδέσμου" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Αποστολή κειμένου" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Εμφάνιση έκδοσης έκδοσης" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Κλειδί επαλήθευσης: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Αίτημα ζεύγους από %s" #: src/service/device.js:853 msgid "Reject" msgstr "Απόρριψη" #: src/service/device.js:858 msgid "Accept" msgstr "Αποδοχή" #: src/service/manager.js:145 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" "Η ανίχνευση έχει απενεργοποιηθεί λόγω του αριθμού των συσκευών σε αυτό το " "δίκτυο." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "Το OpenSSL δεν βρέθηκε" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Η θύρα χρησιμοποιείται ήδη" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Ανταλλαγή πληροφοριών μπαταρίας" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Πλήρης φόρτιση" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Φορτίστηκε" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Η μπαταρία είναι χαμηλή" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% απομένει" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Πρόχειρο" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Μοιραστείτε το περιεχόμενο του πρόχειρου" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Αποστόλη πρόχειρου" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Λήψη πρόχειρου" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Πρόσβαση στις επαφές της συζευγμένης συσκευής" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Εύρεση τηλεφώνου" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Καλέστε τη συζευγμένη συσκευή σας" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" "Επιτρέπει στη συζευγμένη συσκευή να λειτουργεί ως απομακρυσμένο ποντίκι και " "πληκτρολόγιο" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Απομακρυσμένη εισαγωγή" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Αμφίδρομος απομακρυσμένος έλεγχος αναπαραγωγής πολυμέσων" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Άγνωστο" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Κοινή χρήση ειδοποιήσεων με τη συζευγμένη συσκευή" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Ακύρωση ειδοποίησης" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Κλείσιμο ειδοποίησης" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Απάντηση ειδοποίησης" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Ενεργοποίηση ειδοποίησης" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Παρουσίαση" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Χρήση της συζευγμένης συσκευής ως παρουσιαστή" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Εντολές εκτέλεσης" #: src/service/plugins/runcommand.js:15 msgid "" "Run commands on your paired device or let the device run predefined commands " "on this PC" msgstr "" "Εκτελέστε εντολές στη συζευγμένη συσκευή σας ή αφήστε τη συσκευή να " "εκτελέσει προκαθορισμένες εντολές σε αυτόν τον υπολογιστή" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Περιηγηθείτε στο σύστημα αρχείων της συζευγμένης συσκευής" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Προσάρτηση" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Αποπροσάρτηση" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s ανέφερε ένα σφάλμα" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Κοινοποίηση" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Κοινή χρήση αρχείων και διευθύνσεων URL μεταξύ συσκευών" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Η μεταφορά απέτυχε" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s δεν επιτρέπεται το ανέβασμα αρχείων" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Μεταφορά αρχείου" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Λήψη \"%s\" από %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Επιτυχής μεταφορά" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Ελήφθη \"%s\" από %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Εμφάνιση θέσης αρχείου" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Άνοιγμα αρχείου" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Απέτυχε η λήψη του \"%s\" από το %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "Κείμενο κοινοποιημένο από %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "Αποστολή \"%s\" σε %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Απέτυχε η αποστολή του \"%s\" στο %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "Αποστολή αρχείων στο %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "Άνοιγμα όταν τελειώσει" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Στείλτε έναν σύνδεσμο στο %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" "Αποστολή και ανάγνωση SMS της συζευγμένης συσκευής και ειδοποίηση για νέα SMS" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Νέο SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Απάντηση SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Κοινοποίηση SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Ένταση συστήματος" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" "Ενεργοποίηση της συζευγμένης συσκευής για τον έλεγχο της έντασης ήχου του " "συστήματος" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "Δεν βρέθηκε το PulseAudio" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Σίγαση κλήσης" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "Εισερχόμενη κλήση" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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:115 #, 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-ea89821/po/es.po000066400000000000000000001064451477177637400235310ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-25 08:11\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:30 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:24 msgid "GSConnect Team" msgstr "Equipo de GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Compartir archivos, enlaces y texto" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Enviar y recibir mensajes" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Sincronizar el contenido del portapapeles" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Sincronizar los contactos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Sincronizar las notificaciones" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Controlar reproductores multimedia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Controlar el volumen del sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Ejecutar órdenes predefinidas" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Y más…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect en GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Enviar mensaje" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Mensajería" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Equipo de escritorio" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Sincronización de portapapeles" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Reproductores multimedia" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Ratón y teclado" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Control de volumen" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Archivos" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Recibir archivos" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Guardar archivos en" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Compartición" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Batería del dispositivo" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Notificación de batería baja" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Notificación de nivel de carga personalizado" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Notificación de carga completa" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Batería del sistema" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Compartir estadísticas" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Batería" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Órdenes" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Notificaciones de compartición" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Compartir mientras haya actividad" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Aplicaciones" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notificaciones" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contactos" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Llamadas entrantes" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volumen" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Pausar multimedia" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Llamadas en curso" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Silenciar micrófono" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonía" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Atajos de acciones" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Restablecer todo…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Atajos" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Complementos" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimentos" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Antememoria de dispositivo" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Vaciar antememoria…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Compatibilidad SMS heredada" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Montaje automático SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avanzado" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Atajos de teclado" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Configuración del dispositivo" #. 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:367 msgid "Pair" msgstr "Emparejamiento" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "El dispositivo no está emparejado" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Información de cifrado" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Desemparejar" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Al dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Del dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Nada" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Disminuir" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Descubrimiento desactivado" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "Añadir dispositivo por IP…" #: 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:402 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:38 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: 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 "Enviar a %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 "Enviar a dispositivo móvil" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Sincronizar entre sus dispositivos" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d conectado" msgstr[1] "%d conectados" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Editar" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Quitar" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Desactivado" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "Ya está utilizándose %s" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Adolfo Jayme Barrientos , 2018-2019" #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Revisar registro" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Equipo portátil" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Teléfono inteligente" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tableta" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisión" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Desemparejado" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Desconectado" #: src/preferences/service.js:510 msgid "Connected" msgstr "Conectado" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Esperando el servicio…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Pulse para obtener información de solución de problemas" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Pulse para más información" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Marcar número" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Compartir archivo" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Enumerar dispositivos disponibles" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Enumerar todos los dispositivos" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Dispositivo de destino" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Cuerpo del mensaje" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Enviar notificación" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Nombre de la aplicación de la notificación" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Cuerpo de la notificación" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Icono de la notificación" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Identificador de la notificación" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Prueba de conectividad" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Timbrar" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Compartir enlace" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Compartir texto" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Mostrar versión" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Clave de verificación: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Solicitud de emparejamiento de %s" #: src/service/device.js:853 msgid "Reject" msgstr "Rechazar" #: src/service/device.js:858 msgid "Accept" msgstr "Aceptar" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "No se encontró OpenSSL" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Puerto ya en uso" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Intercambiar información sobre la batería" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Carga completa" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d %% cargada" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batería baja" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d %% restante" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Portapapeles" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Compartir el contenido del portapapeles" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Envío a portapapeles" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Recepción desde portapapeles" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Acceder a los contactos del dispositivo emparejado" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Encontrar mi teléfono" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Hacer sonar su dispositivo emparejado" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Entrada remota" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Control remoto de reproducción multimedia bidireccional" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Desconocido" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Compartir notificaciones con el dispositivo emparejado" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Cancelar notificación" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Cerrar notificación" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Notificación de respuesta" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Activar notificación" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Prueba de conectividad: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Presentación" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Usar el dispositivo emparejado como presentador" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Ejecutar órdenes" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Examinar el sistema de archivos del dispositivo emparejado" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s informó de un error" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Compartición" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Compartir archivos y URLs entre dispositivos" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Transferencia fallida" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s no tiene permitido cargar archivos" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Transfiriendo archivo" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Recibiendo «%s» de %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Transferencia exitosa" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Se recibió «%s» de %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Mostrar ubicación del archivo" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Abrir archivo" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Falló la recepción de «%s» desde %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Se envió «%s» a %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Falló el envío de «%s» a %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Abrir al terminar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Enviar un enlace a %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "SMS nuevo (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Responder a SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Compartir SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volumen del sistema" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Activar el dispositivo emparejado para controlar el volumen del sistema" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "No se encontró PulseAudio" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Silenciar llamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Contacto desconocido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Llamada entrante" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Ahora mismo" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Ayer・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "No disponible" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Mensaje grupal" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Usted: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d %% (quedan %d∶%02d)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Servicio no disponible" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Abrir en el navegador" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/et.po000066400000000000000000001026201477177637400235210ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "GSConnecti meeskond" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Jagada faile, linke ja teksti" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Saata ja vastu võtta sõnumeid" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Sünkroonida lõikelaua sisu" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Sünkroonida kontakte" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Sünkroonida teatisi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Juhtida meediamängijaid" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Juhtida süsteemi helitugevust" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Käivitada eelmääratud käsklusi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Ja muud…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect GNOME Shellis" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Saada sõnum" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Sõnumside" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Lauaarvuti" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Lõikelaua sünkroonimine" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Meediamängijad" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Hiir ja klaviatuur" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Helitugevuse juhtimine" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Failid" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Võta faile vastu" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Salvesta failid asukohta" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Jagamine" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Seadme aku" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Tühjeneva aku teade" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Teade kohandatud taseme laadimisest" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Täislaetud aku teade" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Süsteemi aku" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Jaga statistikat" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Aku" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Käsklused" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Jaga teateid" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Jaga, kui on aktiivne" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Rakendused" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Teated" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontaktid" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Sissetulevad kõned" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Helitugevus" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Peata meedia" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Käimasolevad kõned" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Vaigista mikrofon" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonifunktsioon" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Tegevuste otseteed" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Lähtesta kõik…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Otseteed" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Pluginad" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Katseline" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Seadme vahemälu" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Tühjenda vahemälu…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Pärand SMS-tugi" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "SFTP automaathaakimine" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Täpsemad" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Klaviatuuriotseteed" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Seadme seaded" #. 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:367 msgid "Pair" msgstr "Paarita" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Seade on paaritamata" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Krüpteeringu teave" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Eemalda paardumine" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Seadmesse" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Seadmest" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Puudub" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Taasta" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Vaiksem" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Seadmete otsimine…" #: 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 "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:144 msgid "Discovery Disabled" msgstr "Avastamine keelatud" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Saada seadmesse %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 "Saada mobiilseadmesse" #: 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 ühendatud" msgstr[1] "%d ühendatud" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Muuda" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Eemalda" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Keelatud" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s on juba kasutusel" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Täielik KDE Connect'i teostus GNOMEle" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Madis O, 2018." #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Vaata logi üle" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Sülearvuti" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Nutitelefon" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tahvelarvuti" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisioon" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Paaritamata" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Ühendus katkestatud" #: src/preferences/service.js:510 msgid "Connected" msgstr "Ühendatud" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Teenuse ootamine…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Klõpsa, et saada veaotsingul abi" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Klõpsa rohkema teabe saamiseks" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Helista telefoninumbrile" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Jaga faili" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Kuva saadaolevad seadmed" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Kuva kõik seadmed" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Sihtseade" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Sõnumi sisu" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Saada teade" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Teavitava rakenduse nimi" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Teate sisu" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Teate ikoon" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Teate ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Helise" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Jaga linki" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Jaga teksti" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Kuva väljalaskeversiooni" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Paaritamistaotlus seadmelt %s" #: src/service/device.js:853 msgid "Reject" msgstr "Keeldu" #: src/service/device.js:858 msgid "Accept" msgstr "Nõustu" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSLi ei leitud" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Port on juba kasutusel" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Aku infovahetus" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Täielikult laetud" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Laetud" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: aku on tühi" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% jäänud" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Lõikelaud" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Jaga lõikelaua sisu" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Lõikelaua saatmine" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Lõikelaua vastuvõtmine" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Ligipääs ühendatud seadme kontaktide juurde" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Leia mu telefon" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Helista oma seotud seadmele" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Hiirepadi" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Kahesuunaline meediumi kaugjuhtimine" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Teadmata" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Jaga teavitusi seotud seadmega" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Tühista teade" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Sulge teade" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Vasta teatele" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Aktiveeri teade" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Esitlus" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Kasuta seotud seadet presentatsiooniks" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Käivita käsklusi" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Sirvi seotud seadme failisüsteemi" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Haagi" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Haagi lahti" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s teatas vea" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Jaga" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "URL-i ja failide jagamine seadmete vahel" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Ülekanne ebaõnnestus" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s ei tohi faile üles laadida" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Faili edastamine" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "„%s“vastuvõtmine seadmelt %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Ülekanne edukas" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "„%s“vastu võetud seadmelt %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Ava fail" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "„%s“vastuvõtmine seadmest %s ebaõnnestus" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "„%s“saadetud seadmesse %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "„%s“saatmine seadmesse %s ebaõnnestus" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Ava, kui valmis" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Saada link seadmesse %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Uus SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Vasta SMSile" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Jaga SMSi" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Süsteemi helitugevus" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Luba ühendatud seadmel juhtida süsteemi helitugevust" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudiot ei leitud" #: src/service/plugins/telephony.js:16 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:28 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:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Tundmatu kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Sissetulev kõne" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Praegu" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Eile・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minutit" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Pole saadaval" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Grupisõnum" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Sina: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (jäänud %d∶%02d)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Vasta" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Teenus pole saadaval" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Ava brauseris" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/fa.po000066400000000000000000001142671477177637400235110ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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: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:24 msgid "GSConnect Team" msgstr "گروه جی‌اس‌کانکت" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "پرونده‌ها، پیوندها و متن را هم‌رسانی کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "پیام‌ها را فرستاده و دریافت کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "محتوای تخته‌گیره را همگام کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "آشنایان را همگام کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "آگاهی‌ها را همگام کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "پخش‌کننده‌های رسانه را وابپایید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "حجم صدای سامانه را وابپایید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "دستورهای از پیش تعریف‌شده را اجرا کنید" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "و بیش‌تر…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "جی‌اس‌کانکت در پوستهٔ گنوم" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "آگاهی‌ها" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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 "سوار کردن خودکار 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:367 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:390 msgid "Encryption Info" msgstr "اطّلاعات رمزنگاری" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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:144 msgid "Discovery Disabled" msgstr "کشف از کار افتاد" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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 "فرستادن به %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:669 src/preferences/device.js:675 msgid "Edit" msgstr "ویرایش" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "برداشتن" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "از کار افتاده" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s از پیش در حال استفاده است" #: 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:175 msgid "Click for help troubleshooting" msgstr "برای راهنمایی برای عیب‌یابی کلیک کنید" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "برای اطّلاعات بیشتر کلیک کنید" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "شماره‌گیری" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "هم رسانی پرونده" #: src/service/daemon.js:337 msgid "List available devices" msgstr "فهرست افزاره‌های موجود" #: src/service/daemon.js:346 msgid "List all devices" msgstr "فهرست تمامی افزاره‌ها" #: src/service/daemon.js:355 msgid "Target Device" msgstr "افزارهٔ هدف" #: src/service/daemon.js:397 msgid "Message Body" msgstr "متن پیام" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "فرستادن آگاهی" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "نام کارهٔ آگاهی" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "متن آگاهی" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "نقشک آگاهی" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "شناسهٔ آگاهی" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "پینگ" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "زنگ" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "هم‌رسانی پیوند" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "هم‌رسانی متن" #: src/service/daemon.js:505 msgid "Show release version" msgstr "نمایش نگارش ارائه" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "دستگاه بلوتوث در %s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "کلید تأیید هویت: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "درخواست جفت کردن از %s" #: src/service/device.js:853 msgid "Reject" msgstr "رد کردن" #: src/service/device.js:858 msgid "Accept" msgstr "پذیرش" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "به خاطر تعداد افزاره‌های روی این شبکه، کشف از کار افتاد." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "اوپن‌اس‌اس‌ال پیدا نشد" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "درگاه از پیش در حال استفاده است" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "تبادل اطّلاعات باتری" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "شارژ کامل شد" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "⁦%d٪⁩ شارژ شد" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: باتری کم است" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "⁦%d٪⁩ مانده" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "تخته‌گیره" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "محتوای تخته‌گیره را هم‌رسانی کنید" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "فرستادن به تخته‌گیره" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "گرفتن از تخته‌گیره" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "به مخاطبین دستگاه جفت‌سازی شده دسترسی پیدا کنید" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "تلفنم را بیاب" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "زنگ خوردن دستگاه جفت شده شما" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "صفحهٔ موشی" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "قادر کردن افزارهٔ جفت شده برای عمل به عنوان موشی و صفحه‌کلید" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "ورودی دوردست" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "واپایش پخش رسانهٔ دوردست دوطرفه" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "ناشناخته" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "هم‌رسانی آگاهی‌ها با افزارهٔ جفت شده" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "لغو آگاهی" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "بستن آگاهی" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "پاسخ به آگاهی" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "فعال سازی آگاهی" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "پینگ: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "ارائه" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "استفاده از افزارهٔ جفت شده به عنوان ارائه دهنده" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "اجرای دستورها" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "اجرای فرمان‌ها روی افزارهٔ جفت شده‌ةان یا اجازه به افزاره برای اجرای فرمان‌های از پیش تعریف شده روی این رایانه" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "مرور سامانه‌پروندهٔ افزارهٔ جفت شده" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "سوار کردن" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "پیاده کردن" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s خطایی گزارش کرد" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "هم‌رسانی" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "هم‌رسانی پرونده‌ها و نشانی‌ها میان دستگاه‌ها" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "جابه‌جایی شکست خورد" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s اجازهٔ بارگذاری پرونده‌ها را ندارد" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "در حال جابه‌جایی پرونده‌ها" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "در حال دریافت «%s» از %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "جابه‌جایی موفق" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "«%s» از %s دریافت شد" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "نمایش مکان پرونده" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "گشودن پرونده" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "شکست در گرفتن «%s» از %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "متن هم‌رسانده از %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "در حال فرستادن «%s» به %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "شکست در فرستادن «%s» به %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "فرستادن پرونده‌ها به %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "گشودن هنگام اتمام" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "فرستادن پیوندی به %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "پیامک" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "فرستادن یا خواندن پیامک افزارهٔ جفت شده و آگاه شدن از پیامک جدید" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "پیامک جدید (نشانی)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "پاسخ به پیامک" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "هم‌رسانی پیامک" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "حجم صدای سامانه" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "قادر کردن افزارهٔ جفت شده برای واپایش حجم صدای سامانه" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "پالس‌آدیو پیدا نشد" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "خموشی تماس" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "تماس دریافتی" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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:115 #, 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 "با جی‌اس کانکت، پیوندها را با پیامک یا مستقیماً در مرورگر هم‌رسانی کنید." #. 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-ea89821/po/fi.po000066400000000000000000001047301477177637400235130ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-12 13:29\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: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 -toteutus GNOMElle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:24 msgid "GSConnect Team" msgstr "GSConnect-tiimi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Jaa tiedostoja, linkkejä ja tekstiä" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Lähetä ja vastaanota viestejä" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Synkronoi leikepöydän sisältö" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Synkronoi yhteystiedot" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Synkronoi ilmoitukset" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Ohjaa mediasoittimia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Ohjaa järjestelmän äänenvoimakkuutta" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Suorita ennaltamäärättyjä komentoja" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Ja lisää…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect GNOME Shellissä" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Lähetä viesti" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Viestintä" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Työpöytä" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Leikepydän synkronointi" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Mediasoittimet" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Hiiri & näppäimistö" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Äänenvoimakkuuden säätö" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Tiedostot" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Vastaanota tiedostoja" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Tallenna tiedostot nimellä" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Jakaminen" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Laitteen akku" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Ilmoitus akun alhaisesta varaustasosta" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Mukautetun varaustason ilmoitus" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Ilmoitus akun täydestä varaustasosta" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Järjestelmän akku" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Jaa tilastot" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Akku" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Komennot" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Jaa ilmoitukset" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Jaa kun aktiivinen" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Sovellukset" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Ilmoitukset" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Yhteystiedot" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Saapuvat puhelut" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Äänenvoimakkuus" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Keskeytä media" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Meneillään olevat puhelut" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Mykistä mikrofoni" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Puhelinpalvelut" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Toimintojen pikanäppäimet" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Nollaa kaikki…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Pikanäppäimet" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Liitännäiset" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Kokeelliset" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Laitteen välimuisti" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Tyhjennä välimuisti…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Vanhentunut SMS-tuki" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "SFTP:n automaattinen liitäntä" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Lisäominaisuudet" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Pikanäppäimet" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Laitteen asetukset" #. 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:367 msgid "Pair" msgstr "Muodosta laitepari" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Laitteen paritus on poistettu" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Salaustiedot" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Poista paritus" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Laitteelle" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Laitteesta" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Ei mitään" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Palauta" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Madalla" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Laitteiden löytö pois käytöstä" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "Lisää laite IP-osoitteella…" #: 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:402 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:38 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: 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 "Lähetä numeroon %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 "Lähetä mobiililaitteeseen" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Synkronoi laitteidesi välillä" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d yhdistetty" msgstr[1] "%d yhdistetty" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Muokkaa" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Poista" #: src/preferences/device.js:944 src/preferences/device.js:972 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:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s on jo käytössä" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Täydellinen KDE Connect -toteutus Gnomea varten" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Jiri Grönroos \n" "..." #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Tarkista loki" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Kannettava" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Älypuhelin" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tabletti" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisio" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Laiteparia ei muodostettu" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Yhteys katkaistu" #: src/preferences/service.js:510 msgid "Connected" msgstr "Yhdistetty" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Odotetaan palvelua…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Napsauta tästä vianmääritykseen" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Napsauta saadaksesi lisätietoja" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Näppäile numero" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Jaa tiedosto" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Listaa saatavilla olevat laitteet" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Listaa kaikki laitteet" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Kohdelaite" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Viestin runko" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Lähetä ilmoitus" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Ilmoitussovelluksen nimi" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Ilmoituksen runko" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Ilmoituksen kuvake" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Ilmoituksen tunniste" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Soita ääni" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Jaa linkki" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Jaa teksti" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Näytä julkaisuversio" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Vahvistusavain: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Laiteparin muodostuspyyntö laitteelta %s" #: src/service/device.js:853 msgid "Reject" msgstr "Hylkää" #: src/service/device.js:858 msgid "Accept" msgstr "Hyväksy" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL:ää ei löytynyt" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Portti on jo käytössä" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Vaihda akkutietoa" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Täysin ladattu" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d% % ladattu" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Akku lähes tyhjä" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d% % jäljellä" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Leikepöytä" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Jaa leikepöydän sisältö" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Leikepöydän työntö" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Leikepöydän veto" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Käytä parilaitteen yhteystietoja" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Etsi puhelin" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Soita ääni laiteparissa" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Kosketuslevy" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Etäsyöte" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Kaksisuuntainen etämedian toiston ohjaus" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Tuntematon" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Jaa ilmoitukset parilaitteen kanssa" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Peru ilmoitus" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Sulje ilmoitus" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Vastaa ilmoitukseen" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Aktivoi ilmoitus" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Pingaa: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Esitys" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Käytä laiteparia esityslaitteena" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Suorita komentoja" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Selaa laiteparin tiedostojärjestelmää" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Liitä" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Irrota" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s ilmoitti virheestä" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Jaa" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Jaa tiedostoja ja URL-osoitteita laitteiden välillä" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Siirto epäonnistui" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "Laitteella %s ei ole lupaa lähettää tiedostoja" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Siirretään tiedostoa" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Vastaanotetaan “%s” laitteelta %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Siirto valmistui" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Vastaanotettiin “%s” laitteelta %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Näytä tiedoston sijainti" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Avaa tiedosto" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Tiedoston “%s” vastaanotto laitteelta %s epäonnistui" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Lähetettiiin “%s” laitteelle %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Tiedoston “%s” lähettäminen laitteelle %s epäonnistui" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Avaa kun valmis" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Lähetä linkki laitteeseen %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "Tekstiviesti" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Uusi tekstiviesti (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Vastaa tekstiviestiin" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Jaa tekstiviesti" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Järjestelmän äänenvoimakkuus" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Käytä laiteparia järjestelmän äänenvoimakkuuden hallintaan" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudiota ei löytynyt" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Mykistä puhelu" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Tuntematon yhteystieto" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Saapuva puhelu" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Juuri nyt" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Eilen・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuutti" msgstr[1] "%d minuuttia" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Ei saatavilla" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Ryhmäviesti" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Sinä: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d% % (%d∶%02d jäljellä)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Vastaa" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Palvelu ei ole käytettävissä" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Avaa selaimessa" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/fr.po000066400000000000000000001066571477177637400235360ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:05\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:30 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:24 msgid "GSConnect Team" msgstr "L'équipe de GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 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:49 msgid "Send and receive messages" msgstr "Envoyer et recevoir des messages" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Synchroniser le contenu du presse-papiers" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Synchroniser les contacts" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Synchroniser les notifications" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Contrôler les lecteurs de médias" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Contrôle le volume du système" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Exécuter des commandes prédéfinies" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Et bien plus encore…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect dans GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Envoyer le message" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Messagerie" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Ordinateur de bureau" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Synchroniser le presse-papiers" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Lecteurs multimédia" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Souris et clavier" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Gestion du volume" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Fichiers" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Recevoir les fichiers" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Enregistrer les fichiers dans" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Partage" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Batterie de l'appareil" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Notification de batterie faible" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Notification de batterie à un niveau de charge personnalisé" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Notification de batterie entièrement chargée" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Batterie système" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Partager des statistiques" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Batterie" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Commandes" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Synchroniser les notifications" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Partager quand l'appareil est actif" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Applications" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notifications" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contacts" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Appels entrants" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Mettre les médias en pause" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Appels en cours" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Mettre le microphone en sourdine" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Téléphonie" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Raccourcis d'actions" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Tout réinitialiser…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Raccourcis" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Greffons" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Expérimental" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Cache de l'appareil" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Vider le cache…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Ancienne gestion des SMS" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Montage automatique SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avancé" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Raccourcis clavier" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Paramètres de l'appareil" #. 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:367 msgid "Pair" msgstr "Associer" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "L'appareil n'est pas pairé" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informations de chiffrement" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Dissocier" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Vers l'Appareil" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Depuis l'Appareil" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Ne rien faire" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Restaurer" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Réduire" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Découverte désactivée" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Envoyer vers %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 "Envoyer vers l'appareil mobile" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Synchronisez entre vos appareils" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d Connecté" msgstr[1] "%d Connectés" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Modifier" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Supprimer" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Désactivé" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s est déjà utilisé" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Mickaël Coiraton " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Vérifier les logs" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Ordinateur portable" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablette" #: src/preferences/service.js:480 msgid "Television" msgstr "Télévision" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Dissocié" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Déconnecté" #: src/preferences/service.js:510 msgid "Connected" msgstr "Connecté" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "En attente du service…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Cliquer pour l'aide de dépannage" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Cliquer pour plus d'informations" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Composer le numéro" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Partager un fichier" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Liste des appareils disponibles" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Liste de tous les appareils" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Appareil cible" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Corps du message" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Envoyer la notification" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Nom de l'application de la notification" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Corps de la notification" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Icône de la notification" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID de la notification" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Faire sonner" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Partager un lien" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Partager du texte" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Montrer la sortie de version" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Clé de vérification : %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Demande d'association depuis %s" #: src/service/device.js:853 msgid "Reject" msgstr "Rejeter" #: src/service/device.js:858 msgid "Accept" msgstr "Accepter" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL introuvable" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Port déjà utilisé" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Échanger les informations sur la batterie" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Complètement chargé" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Chargé" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: la batterie est faible" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d %% restant" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Presse-papiers" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Partager le contenu du presse-papiers" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Envoyer le presse-papiers" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Récupérer le presse-papiers" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Accéder aux contacts de l'appareil appairé" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Trouver mon téléphone" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Faire sonner l'appareil appairé" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Pavé tactile" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Entrée distante" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Contrôle à distance bidirectionnel de lecture de médias" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Inconnu" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Partager les notifications avec l'appareil appairé" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Annuler la notification" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Fermer la notification" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Répondre à la notification" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Active la notification" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping : %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Présentation" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Utiliser l'appareil appairé comme présentateur" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Exécuter des commandes" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Parcourir le système de fichiers de l'appareil appairé" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Monter" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Démonter" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s a signalé une erreur" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Partage" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Partager des fichiers et des URL entre les appareils" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Échec du transfert" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, 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:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Transfert du fichier" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Réception de « %s » depuis %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Transfert réussi" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Reçu « %s » depuis %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Afficher l'emplacement du fichier" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Ouvrir le fichier" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Échec de réception de « %s » depuis %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "« %s » envoyé vers %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Échec d'envoi de « %s » vers %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Ouvrir une fois fini" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Envoyer un lien vers %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Nouveau SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Répondre au SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Partager le SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volume du système" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio introuvable" #: src/service/plugins/telephony.js:16 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:28 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:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Contact inconnu" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Appel entrant" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "À l'instant" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Hier・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minute" msgstr[1] "%d minutes" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Indisponible" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Message de groupe" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Vous: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d %% (%d∶%02d restant)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Répondre" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Service indisponible" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Ouvrir dans le navigateur" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/fy.po000066400000000000000000001020201477177637400235210ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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: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 implementaasje foar GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:24 msgid "GSConnect Team" msgstr "GSConnect Team" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Triemen, ferwiizingen en tekst te ferstjoeren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Berjochten te ferstjoeren en binnen te krijen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Klemboerd ynhâld syngronisearje" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Kontakten syngronisearje" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Notifikaasjes te syngronisearjen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Media spilers te bestjoeren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Lûdsynstellings fan it systeem te feroarjen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Fan te foaren definiearre kommando's ût te fieren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "En meer…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect yn GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Berjocht Ferstjoere" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Berjochten" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Desktop" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Klemboerd Syngronisaasje" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Mediaspilers" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Mûs & Toetseboerd" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Lûdsynstellings" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Triemen" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Triemen Ûntfange" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Triemen bewarje yn" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Diele" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Apperaat batterij" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Lege Batterij Notifikaasje" #: 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 "Folslein Opladen Notifikaasje" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Systeem Batterij" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Statistiken Diele" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Baterij" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Kommandos" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Notifikaasjes Diele" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Diele Wannear Aktyf" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Applikaasjes" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notifikaasjes" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontakten" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Ynkommende Tillefoantsjes" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Lûdsynstellingen" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Media Skoftsje" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Útgeande Tillefoantsjes" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Mikrofoan bedimje" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefoan" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Aksje Fluchtoetsen" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Alles Werom Sette…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Ferkoartingen" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Plugins" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Eksperimenteel" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Apparaat Cache" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Cache leechje…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Efterhelle SMS ûndersteuning" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "SFTP Automatysk ferbine" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avansearre" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Toetseboerd Fluchtoetsen" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Apparaat Ynstellingen" #. 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:367 msgid "Pair" msgstr "Keppelje" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Apparaat is net keppele" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Fersifering Ynformaasje" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Ûntkeppelje" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Nei Apparaat" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Fan Apparaat" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Neat" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Werom Sette" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Omleech" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Om apparaten sykje…" #: 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 "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:144 msgid "Discovery Disabled" msgstr "Ûntdekken Útskeakelje" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Ferstjoer nei %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 "Nei Mobiel Apparaat ferstjoere" #: 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] "%d Ferbûn" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Feroarje" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Fuortsmite" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Útskeakele" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s wurd ol brûkt" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "In folsleine KDE Connect implementaasje foar GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Tjipke van der Heide " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Loch Besjen" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Snoadtillefoan" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Telefyzje" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Net Keppele" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Ferbining ferbrutsen" #: src/preferences/service.js:510 msgid "Connected" msgstr "Ferbûn" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Op in ferbining oan it wachtsjen…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Nûmer Skilje" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Triem Ferstjoere" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Beskikbere apparaten sjen litte" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Olle apparaten sjen litte" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Doel Apparaat" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Berjocht Ynhâld" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Notifikaasje Ferstjoere" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Notifikaasje Applikaasje Namme" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Notifikaasje Haadtekst" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Notifikaasje ôfbylding" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Notifikaasje ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Ôfgean Litte" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Keppeling Diele" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Tekst Diele" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Útjefte Ferzje Sjen Litte" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Keppelingsfersyk fan %s" #: src/service/device.js:853 msgid "Reject" msgstr "Ôfslaan" #: src/service/device.js:858 msgid "Accept" msgstr "Tastean" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL net fûn" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Poarte wurd ol brûkt" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Foslein Opladen" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Batterij is leech" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% te gean" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Klemboerd" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Klemboerd Ferstjoere" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Klemboerd Ophelje" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Fyn Myn Tillefoan" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mûsmatte" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Ûnbekend" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Notifikaasje Ôfbrekke" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Notifikaasje Ôfslûte" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Notifikaasje foar Antwurdzjen" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Notifikaasjes Ynskeakelje" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Presentaasje" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Komandos Útfiere" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Oankeppelje" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Loskeppelje" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Diele" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Oerdracht Mislearre" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s mei gjin triemen uploade" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Triemen Oersette" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "\"%s\" fan %s ûntfong" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Oerset Slagge" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "\"%s\" fan %s ûntfong" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Triem Iepenje" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Koe \"%s\" net fan %s binnen krije" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "“%s” nei %s ferstjoere" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Ferstjoeren fan \"%s\" nei %s mislearre" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Iepenje wannear klear" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Keppeling nei %s ferstjoere" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Nije SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Op SMS Reagearje" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "SMS Diele" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Systeem Lûdsynstellings" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio net fûn" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Tillefoantsje Bedimje" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Ûnbekend Kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Binnenkommende Tillefoantsjes" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "No krekt" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Juster・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minút" msgstr[1] "%d minuten" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Net beskikber" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Berjochten Groepearje" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Do: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d Te Gean)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Reagearje" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Tsjinst Ûnbeskikber" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Yn Browser Iepenje" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/gl.po000066400000000000000000001023131477177637400235120ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "Equipo GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Compartir ficheiros, ligazóns e texto" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Enviar e recibir mensaxes" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Sincronizar contido do portapapeis" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Sincronizar contactos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Sincronizar notificacións" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Controlar reprodutores multimedia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Controla o volume do sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Executar ordes predefinidas" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "E máis…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect na Shell de GNOME" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Enviar mensaxe" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Mensaxes" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Escritorio" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Sincronizar portapapeis" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Reprodutores multimedia" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Rato e teclado" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Control de volume" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Ficheiros" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Recibir ficheiros" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Gardar ficheiros en" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Compartindo" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Batería do dispositivo" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Notificación de batería baixa" #: 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 "Notificación de batería a tope de carga" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Batería do sistema" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Compartir estatísticas" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Batería" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Ordes" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Compartir notificacións" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Compartir cando estea activo" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Aplicativos" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notificacións" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contactos" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Chamadas entrantes" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Deter reprodución" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Chamadas saíntes" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Silenciar micrófono" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonía" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Atallos de acción" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Reiniciar todo…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Atallos" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Engadidos" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimental" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Caché do dispositivo" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Despexar a caché…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Compatibilidade con SMS herdados" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Montado automático SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avanzado" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Atallos de teclado" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Configuración do dispositivo" #. 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:367 msgid "Pair" msgstr "Enparellar" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "O dispositivo non está emparellado" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Info de cifrado" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Desemparellar" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "A dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Do dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Ningún" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Baixar" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Buscando dispositivos…" #: 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 "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:144 msgid "Discovery Disabled" msgstr "Descuberta desactivada" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Enviar a %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 "Enviar a dispositivo móbil" #: 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 Conectado" msgstr[1] "%d Conectados" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Editar" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Retirar" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Desactivado" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s xa está en uso" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "tradutor" #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Revisar o rexistro" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Portátil" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Móbil" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tableta" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisión" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Desemparellado" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Desconectado" #: src/preferences/service.js:510 msgid "Connected" msgstr "Conectado" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Agardar polo servizo…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Prema para obter axuda de solucións" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Premer para ter máis información" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Número en dial" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Compartir ficheiro" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Lista de dispositivos dispoñíbeis" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Listar todos os dispositivos" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Dispositivo de destino" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Corpo da mensaxe" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Enviar a notificación" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Nome da app notificada" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Corpo da notificación" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Icona da notificación" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID de notificación" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Timbre" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Compartir ligazón" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Compartir texto" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Amosar a versión da edición" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Petición de emparellado desde %s" #: src/service/device.js:853 msgid "Reject" msgstr "Rexeitar" #: src/service/device.js:858 msgid "Accept" msgstr "Aceptar" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "Non se atopou o OpenSSL" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "O porto xa está sendo utilizado" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Carga completa" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: A batería está baixa" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "Queda o %d%%" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Portapapeis" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Entregar do portapapeis" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Recoller no portapapeis" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Atopar o meu teléfono" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Área de rato" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Descoñecido" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Anular a notificación" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Pechar a notificación" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Responder a notificación" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Activar a notificación" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Presentación" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Executar ordes" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Compartir" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Fallou a transferencia" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s non permite cargar ficheiros" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Transferindo o ficheiro" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Recibindo \"%s\" de %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Transferencia correcta" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Recibíronse \"%s\" desde %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Abrir ficheiro" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Fallou a recepción de \"%s\" desde %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Enviar \"%s\" a %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Fallou o envío de \"%s\" a %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Abrir cando estea feito" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Enviar unha ligazón a %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Novo SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Responder a SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Compartir SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volume do sistema" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "Non se atopou PulseAudio" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Silenciar chamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Contacto descoñecido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Chamada entrante" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Agora mesmo" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Onte・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Non dispoñíbel" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Mensaxe de grupo" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Vostede: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d Restante)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Servizo non dispoñíbel" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Abrir no navegador" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/he.po000066400000000000000000001111311477177637400235020ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:24 msgid "GSConnect Team" msgstr "צוות GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "שיתוף קבצים, קישורים וטקסט" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "שליחה וקבלת הודעות" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "סנכרון תוכן לוח הגזירים" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "סנכרון אנשי קשר" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "סנכרון התרעות" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "שליטה בפקדי המדיה" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "שליטה בעצמת השמע של המערכת" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "הפעלת פקודות מוגדרות מראש" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "ועוד…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect במעטפת GNOME" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "התרעות" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "מידע על הצפנה" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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:144 msgid "Discovery Disabled" msgstr "גילוי מושבת" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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] "אחד מחובר" msgstr[1] "" msgstr[2] "%d מכשירים מחוברים" msgstr[3] "%d מחוברים" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "עריכה" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "הסרה" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "מושבת" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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:175 msgid "Click for help troubleshooting" msgstr "יש ללחוץ לעזרה בטיפול בבעיות" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "יש ללחוץ למידע נוסף" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "חיוג מספר" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "שיתוף קובץ" #: src/service/daemon.js:337 msgid "List available devices" msgstr "רשימת מכשירים זמינים" #: src/service/daemon.js:346 msgid "List all devices" msgstr "רשימת כל המכשירים הזמינים" #: src/service/daemon.js:355 msgid "Target Device" msgstr "מכשיר יעד" #: src/service/daemon.js:397 msgid "Message Body" msgstr "גוף ההודעה" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "שליחת התרעה" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "שם יישום להתרעה" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "גוף התרעה" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "סמליל התרעה" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "מזהה התרעה" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "פינג" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "צלצול" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "שיתוף קישור" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "שיתוף טקסט" #: src/service/daemon.js:505 msgid "Show release version" msgstr "הצגת גרסת שחרור" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "התקן בלוטות׳ ב־%s" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "אימות מפתח: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "בקשת צימוד מ־%s" #: src/service/device.js:853 msgid "Reject" msgstr "דחייה" #: src/service/device.js:858 msgid "Accept" msgstr "אישור" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "הגילוי הושבת עקב מספר המכשירים על הרשת." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "‏ OpenSSL לא נמצא" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "פתחה כבר בשימוש" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "החלפת המידע על הסוללה" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "טעינה מלאה" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "‏%d%% טעון" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "‏%s: הסוללה חלשה" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "נותרו %d%%" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "לוח הגזירים" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "שיתוף תוכן לוח הגזירים" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "שליחת לוח הגזירים" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "קבלת לוח הגזירים" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "גישה לאנשי הקשר של המכשיר המצומד" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "מציאת הטלפון שלי" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "צלצול לטלפון המצומד שלך" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "משטח לעכבר" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "לאפשר מכשיר המצומד לשמש כעכבר ומקלדת מרוחקים" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "קלט מרוחק" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "בקרת הפעלת מדיה מרחוק דו־כיוונית" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "לא ידוע" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "שיתוף התרעות עם המכשיר המצומד" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "ביטול התרעה" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "סגירת התרעה" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "תגובה בהודעה" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "הפעלת התרעה" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "פינג: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "מצגת" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "שימוש במכשיר המצומד כמצגת" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "הרצת פקודות" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "הרצת פקודות על המכשיר המצומד שלך או לתת למכשר להריץ פקודות מוגדרות מראש על המחשב השולחני הזה" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "עיון במערכת הקבצים של המכשיר המצומד" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "עיגון" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "ביטול עיגון" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "‏%s דיווח על שגיאה" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "שיתוף" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "שיתוף קבצים וכתובות URL בין מכשירים" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "ההעברה כשלה" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "‏%s לא מאפשר להעלות קבצים" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "מעביר קובץ" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "‏„%s” מתקבל מ־„%s”" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "ההעברה הצליחה" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "‏„%s” התקבל מ־„%s”" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "הצגת מיקום הקובץ" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "פתיחת הקובץ" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "ארע כשל בקבלת ‏„%s” מ־„%s”" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "טקסט משותף על ידי %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "‏„%s” נשלח כעת אל „%s”" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "אירעה שגיאה בשליחת „%s” אל „%s”" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "שליחת קבצים אל %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "פתיחה עם סיום" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "שליחת קישור אל %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "מסרון SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "שליחת וקריאת מסרוני SMS של המכשיר המצומד וקבלת התרעות על מסרון חדש" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "מסרון SMS חדש (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "לענות ל־SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "שיתוף SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "עצמת השמע של המערכת" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "לאפשר למכשיר המצומד לשלוא בעצמת השמע של המערכת" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "‏PulseAudio לא נמצא" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "השתקת שיחה" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "שיחה נכנסת" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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:115 #, 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-ea89821/po/hu.po000066400000000000000000001071721477177637400235340ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-02-12 23:47\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: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 implementáció GNOME-hoz" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:24 msgid "GSConnect Team" msgstr "A GSConnect csapata" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 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:49 msgid "Send and receive messages" msgstr "Üzenetek küldése és fogadása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Vágólap tartalmának szinkronizálása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Névjegyek szinkronizálása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Értesítések szinkronizálása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Médialejátszók irányítása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Rendszerhangerő vezérlése" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Előre megadott parancsok végrehajtása" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "És még sok más…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect a GNOME Shellben" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Üzenet küldése" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Üzenetküldés" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Asztali gép" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Vágólap-szinkronizálás" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Médialejátszók" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Egér és billentyűzet" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Hangerőszabályzó" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Fájlok" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Fájlok fogadása" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Fájlok mentése ide" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Megosztás" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Eszköz akkumulátora" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Alacsony töltöttség értesítés" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Szabadon állítható feltöltöttségi értesítés" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Teljes töltöttség értesítés" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Rendszer-akkumulátor" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Statisztikák megosztása" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Akkumulátor" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Parancsok" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Értesítések megosztása" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Megosztás aktív használat közben" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Alkalmazások" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Értesítések" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Névjegyek" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Bejövő hívások" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Hangerő" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Média szüneteltetése" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Folyamatban lévő hívások" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Mikrofon némítása" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonálás" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Műveletek gyorsbillentyűi" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Összes visszaállítása…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Gyorsbillentyűk" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Bővítmények" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Kísérleti" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Eszköz gyorsítótár" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Gyorsítótár ürítése…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Régi fajta SMS támogatás" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "SFPT automatikus csatolása" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Speciális" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Gyorsbillentyűk" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Eszköz beállításai" #. 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:367 msgid "Pair" msgstr "Párosítás" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Az eszköz nincs párosítva" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Titkosítási információ" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Párosítás megszűntetése" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Eszközre" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Eszközről" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Semmi" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Visszaállítás" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Halkítás" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Felderítés letiltva" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "Eszköz hozzáadása IP szerint…" #: 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:402 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:38 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: 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 "Küldés neki: %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 "Küldés mobileszközre" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Szinkronizálás az eszközei között" #: src/extension.js:159 #, 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:669 src/preferences/device.js:675 msgid "Edit" msgstr "Szerkesztés" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Eltávolítás" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Letiltva" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s már használatban van" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Teljes KDE Connect implementáció GNOME-hoz" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Báthory Péter " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Napló átnézése" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Okostelefon" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Táblagép" #: src/preferences/service.js:480 msgid "Television" msgstr "TV" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Nincs párosítva" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Nincs csatlakoztatva" #: src/preferences/service.js:510 msgid "Connected" msgstr "Csatlakoztatva" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Várakozás a szolgáltatásra…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Kattintson ide, hogy segítséget kapjon a hibaelhárításhoz" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "További információért kattintson ide" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Telefonszám hívása" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Fájl megosztása" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Elérhető eszközök listázása" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Összes eszköz listázása" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Céleszköz" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Üzenet szövege" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Értesítés küldése" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Értesítés alkalmazásnév" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Értesítés törzs" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Értesítés ikon" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Értesítés ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Csörgetés" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Hivatkozás megosztása" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Szöveg megosztása" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Kiadás verziószámának megjelenítése" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Ellenőrző kulcs: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Párosítási kérelem innen: %s" #: src/service/device.js:853 msgid "Reject" msgstr "Elutasítás" #: src/service/device.js:858 msgid "Accept" msgstr "Elfogadás" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "Az OpenSSL nem található" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "A port már használatban van" #: src/service/plugins/battery.js:15 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:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Teljesen feltöltve" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% töltöttség" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: alacsony töltöttség" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% van hátra" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Vágólap" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Vágólap tartalmának megosztása" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Vágólap küldése" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Vágólap fogadása" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "A párosított eszköz névjegyeinek elérése" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Keresd meg a telefonom" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "A párosított eszköz megcsörgetése" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Egérpad" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Távoli bevitel" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Kétirányú médialejátszó-távirányító" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Ismeretlen" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Értesítések megosztása a párosított eszközzel" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Értesítés megszakítása" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Értesítés bezárása" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Válasz értesítésre" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Értesítés aktiválása" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Prezentáció" #: src/service/plugins/presenter.js:13 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:13 msgid "Run Commands" msgstr "Parancsok futtatása" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 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:21 msgid "Mount" msgstr "Csatolás" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Leválasztás" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s hibát jelzett" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Megosztás" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Fájlok és URL-ek megosztása eszközök között" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Sikertelen átvitel" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, 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:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Fájl átvitele" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "„%s” fogadása innen: %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Sikeres átvitel" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "„%s” fogadva innen: %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Fájl helyének megjelenítése" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Fájl megnyitása" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, 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:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "„%s” elküldve ide: %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "„%s” küldése %s eszközre nem sikerült" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Megnyitás ha kész" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Hivatkozás küldése ide: %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Új SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "SMS válasz" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "SMS megosztása" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Rendszerhangerő" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio nem található" #: src/service/plugins/telephony.js:16 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:28 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:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Ismeretlen névjegy" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Bejövő hívás" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Épp most" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Tegnap・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d perc" msgstr[1] "%d perc" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Nem érhető el" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Csoportos üzenet" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Te: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d van hátra)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Válasz" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "A szolgáltatás nem érhető el" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Megnyitás böngészőben" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/id.po000066400000000000000000000776201477177637400235200ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "Tim GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Membagikan berkas, tautan, dan teks" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Mengirim serta menerima pesan" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Menyinkronkan isi papan klip" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Menyinkronkan kontak" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Menyinkronkan notifikasi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Mengendalikan volume sistem" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Dan banyak lagi…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect di GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Kirim Pesan" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Perpesanan" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Desktop" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Sinkronisasi Papan Klip" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Pemutar Media" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Tetikus & Papan Ketik" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Berkas-berkas" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Terima berkas-berkas" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Simpan berkas-berkas di" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Berbagi" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Baterai Perangkat" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Notifikasi Baterai Lemah" #: 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 "Notifikasi Baterai Terisi Penuh" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Baterai Sistem" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Bagikan Statistik" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Baterai" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Bagikan Notifikasi" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Aplikasi-aplikasi" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notifikasi" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontak" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Panggilan Masuk" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volume" #: 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 "Panggilan Aktif" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Bisukan Mikrofon" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telepon" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Pintasan Tindakan" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Pintasan" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Plugin-plugin" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Eksperimental" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Cache Perangkat" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Hapus Cache…" #: 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 "Lanjutan" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Pintasan Papan Ketik" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Pengaturan Perangkat" #. 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:367 msgid "Pair" msgstr "Hubungkan" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Perangkat tidak terhubung" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informasi Enkripsi" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Putuskan" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Ke Perangkat" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Dari Perangkat" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Tidak Ada" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Pulihkan" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Kecilkan" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Mencari perangkat…" #: 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 "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:144 msgid "Discovery Disabled" msgstr "" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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:402 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:38 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: 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 "Kirim ke %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 terhubung" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Sunting" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Hapus" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Dinonaktifkan" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s sudah digunakan" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Implementasi lengkap KDE Connect untuk GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "@liimee" #: 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 "Laptop" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Ponsel" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisi" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Tidak terhubung" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Terputus" #: src/preferences/service.js:510 msgid "Connected" msgstr "Terhubung" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Menunggu layanan…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Klik untuk informasi lebih lanjut" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Bagikan Berkas" #: src/service/daemon.js:337 msgid "List available devices" msgstr "" #: src/service/daemon.js:346 msgid "List all devices" msgstr "" #: src/service/daemon.js:355 msgid "Target Device" msgstr "" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Isi Pesan" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Kirim Notifikasi" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Isi Notifikasi" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Ikon Notifikasi" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID Notifikasi" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Dering" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Bagikan Tautan" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Bagikan Teks" #: src/service/daemon.js:505 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Permintaan Dihubungkan dari %s" #: src/service/device.js:853 msgid "Reject" msgstr "Tolak" #: src/service/device.js:858 msgid "Accept" msgstr "Terima" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "OpenSSL tidak ditemukan" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Port sudah digunakan" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Terisi Penuh" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Terisi" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Baterai lemah" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% tersisa" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Papan Klip" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Bagikan isi papan klip" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Dorong Papan Klip" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Tarik Papan Klip" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Temukan Ponselku" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Tutup Notifikasi" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Balas Notifikasi" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Presentasi" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Gunakan perangkat terhubung sebagai presenter" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Jalankan Perintah" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Muat" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s melaporkan sebuah kesalahan" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Bagikan" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s tidak diizinkan untuk mengunggah berkas" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Menerima “%s” dari %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "“%s” dari %s diterima" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Buka Berkas" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Gagal menerima “%s” dari %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "“%s” dikirim ke %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Gagal mengirim “%s” ke %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Buka saat selesai" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Kirim tautan ke %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "SMS Baru (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Balas SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volume Sistem" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio tidak ditemukan" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "Panggilan masuk" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Baru saja" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Kemarin • %s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d menit" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Tidak tersedia" #: 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 "Anda: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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 "%d%% (Memperkirakan…)" #. 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 Sampai Penuh)" #. 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 Tersisa)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Balas" #. 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 "Layanan Tidak Tersedia" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Buka di Browser" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/it.po000066400000000000000000001052531477177637400235320ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "Team GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Condividere file, link e testi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Inviare e ricevere messaggi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Sincronizzare il contenuto degli appunti" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Sincronizzare i contatti" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Sincronizzare le notifiche" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Controllare i contenuti multimediali" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Controllare il volume di sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Eseguire comandi predefiniti" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "E altro…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect su GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Invia un messaggio" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Messaggi" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Desktop" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Sincronizzazione appunti" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Riproduzione multimediale" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Mouse e tastiera" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Controllo volume" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "File" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Ricezione file" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Salva i file su" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Condivisione" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Batteria del dispositivo" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Notifica di batteria scarica" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Notificare il raggiungimento del livello personalizzato di carica" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Notifica di ricarica completa" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Batteria di sistema" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Condivisione statistiche" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Batteria" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Comandi" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Condividi notifiche" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Condividi quando attivo" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Applicazioni" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notifiche" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contatti" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Chiamate in entrata" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Metti in pausa il media" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Chiamate in uscita" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Disattiva il microfono" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonia" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Scorciatoie azioni" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Ripristina tutte…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Scorciatoie" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Plugin" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Sperimentale" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Cache del dispositivo" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Cancella cache…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Supporto SMS legacy" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Montaggio automatico SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avanzate" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Scorciatoie da tastiera" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Impostazioni dispositivo" #. 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:367 msgid "Pair" msgstr "Accoppia" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Il dispositivo è disaccoppiato" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informazioni di criptazione" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Disaccoppia" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Al dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Dal dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Niente" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Ripristina" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Riduci" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Ricerca disattiva" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Invia a %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 "Invia al dispositivo mobile" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Sincronizza tra i dispositivi connessi" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d connesso" msgstr[1] "%d connessi" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Modifica" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Rimuovi" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Disabilitato" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s già in uso" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Jimmy Scionti " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Log delle Revisioni" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisione" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Disaccoppiato" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Disconnesso" #: src/preferences/service.js:510 msgid "Connected" msgstr "Connesso" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "In attesa del servizio…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Click per la risoluzione del problema" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Click per altre informazioni" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Componi numero" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Invia file" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Elenco dei dispositivi disponibili" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Elenco di tutti i dispositivi" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Dispositivo di destinazione" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Corpo del messaggio" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Invia notifica" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Nome app di notifica" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Corpo della notifica" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Icona di notifica" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID della notifica" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Squilla" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Condividi collegamento" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Invia testo" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Mostrare versione di rilascio" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Chiave di verifica: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Richiesta di accoppiamento da %s" #: src/service/device.js:853 msgid "Reject" msgstr "Rifiuta" #: src/service/device.js:858 msgid "Accept" msgstr "Accetta" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL non trovato" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Porta già in uso" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Scambiare informazioni sulla batteria" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Completamente carica" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% carico" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batteria quasi scarica" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% rimanenti" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Appunti" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Condividere il contenuto degli appunti" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Invia appunti" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Ricevi appunti" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Accedere ai contatti del dispositivo" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Trova il mio telefono" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Fare squillare il dispositivo" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Immissione remota" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Controllo di riproduzione multimediale bidirezionale" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Sconosciuto" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Condividere le notifiche con il dispositivo" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Cancella notifica" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Chiudi notifica" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Rispondi alla notifica" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Attivare le notifiche" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Presentazione" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Usa il dispositivo come presentatore" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Esegui comandi" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Sfogliare il file system del dispositivo" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Monta" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Smonta" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s ha segnalato un errore" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Invia file" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Condividere file e URL tra dispositivi" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Trasferimento fallito" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s non può caricare file" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Trasferimento file" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Ricezione in corso di \"%s\" da %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Trasferimento riuscito" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Ricevuto \"%s\" da %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Mostra posizione file" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Apri file" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Ricezione fallita di \"%s\" da %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "\"%s\" inviato a %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Invio fallito di \"%s\" a %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Apri al termine" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Invia collegamento a %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Nuovo SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Rispondi all'SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Condividi SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volume di sistema" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio non trovato" #: src/service/plugins/telephony.js:16 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:28 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:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Contatto sconosciuto" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Chiamata in arrivo" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Proprio ora" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Ieri・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minuti" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Non disponibile" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Messaggio di gruppo" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Tu: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d Rimanenti)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Rispondi" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Servizio non disponibile" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Apri nel browser" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/ko.po000066400000000000000000001046021477177637400235240ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "GSConnect 팀" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "파일과 링크, 텍스트 등을 공유합니다" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "메시지를 주고 받기" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "클립보드 내역 동기화" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "연락처 동기화" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "알림 동기화" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "미디어 플레이어 제어" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "시스템 볼륨 제어" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "정의된 명령 실행" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "더 보기…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GNOME Shell의 GSConnect" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "알림" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "암호화 정보" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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: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는 그놈 쉘이 잠겨있을 때에도 동작합니다" #: 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:144 msgid "Discovery Disabled" msgstr "Discovery 비활성화" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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 연결됨" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "편집" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "제거" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "사용 안 함" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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의 호환 기능" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "UtsushimiNeneka(네네카)" #: 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 "TV" #: 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:175 msgid "Click for help troubleshooting" msgstr "클릭하여 문제해결 도움 받기" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "자세한 내용을 보려면 여기를 클릭하십시오." #: src/service/daemon.js:280 msgid "Dial Number" msgstr "전화번호" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "파일 공유" #: src/service/daemon.js:337 msgid "List available devices" msgstr "사용 가능한 장치 목록" #: src/service/daemon.js:346 msgid "List all devices" msgstr "모든 장치 보기" #: src/service/daemon.js:355 msgid "Target Device" msgstr "대상 기기" #: src/service/daemon.js:397 msgid "Message Body" msgstr "메시지 내용" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "알림 보내기" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "알림 앱 이름" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "알림 내용" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "알림 아이콘" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "알림 ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "핑" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "벨" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "링크 공유" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "텍스트 공유" #: src/service/daemon.js:505 msgid "Show release version" msgstr "릴리즈 버전 보기" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "인증 키: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "%s(으)로부터 연결 요청됨" #: src/service/device.js:853 msgid "Reject" msgstr "거부" #: src/service/device.js:858 msgid "Accept" msgstr "동의" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "OpenSSL을 찾을 수 없음" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "포트가 사용 중입니다" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "배터리 정보 교환" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "충전 완료" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% 충전됨" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: 배터리가 부족합니다" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% 남음" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "클립보드" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "클립보드 내용 동기화" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "클립보드 보내기" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "클립보드 받기" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "페어링된 장치의 연락처 보기" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "내 휴대전화 찾기" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "페어링된 장치 벨소리 울리기" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "트랙패드" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "페어링한 장치를 원격 마우스 및 키보드로 사용할 수 있도록 함" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "원격 입력" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "알 수 없음" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "페어링한 장치와 알림 공유" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "알림 취소" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "알림 닫기" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "알림 답장" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "알림 활성화" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "연락: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "프레젠테이션" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "페어링한 장치를 프레젠터로 사용" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "명령 실행" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "페어링한 장치 파일시스템 탐색" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "마운트" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "마운트 해제" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "공유" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "장치간 파일과 URL 공유" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "전송 실패" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s로 파일을 올릴 수 없습니다." #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "파일 전송 중" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "\"%s\"를 %s에서 받는 중" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "전송 성공" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "\"%s\"를 %s에서 받았습니다." #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "파일 위치 표시" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "파일 열기" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "\"%s\"를 %s에서 받아오는 데 실패했습니다." #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "%s(으)로부터 텍스트가 공유됨" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "\"%s\" 파일을 %s(으)로 보내는 중" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "\"%s\"를 %s에 보내는 데 실패했습니다." #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "%s로 파일 보내기" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "완료되면 열기" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "%s로 링크 보내기" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "메시지" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "페어링한 장치로 SMS를 읽거나 보내고 새 SMS가 오면 알림받기" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "새 메시지" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "메시지 답장" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "메시지 공유" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "시스템 볼륨" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "페어링된 장치의 시스템 볼륨 제어 활성화" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio를 찾을 수 없음" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "무음 통화" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "수신 전화" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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분" #: 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명" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:115 #, 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로 링크를 공유하여 웹 브라우저에서 열거나 메시지를 통해 공유합니다." #. 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-ea89821/po/lt.po000066400000000000000000001067321477177637400235400ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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: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 įgyvendinimas, skirtas GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:24 msgid "GSConnect Team" msgstr "GSConnect komanda" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Bendrinti failus, nuorodas ir tekstą" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Siųsti ir gauti žinutes" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Sinchronizuoti iškarpinės turinį" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Sinchronizuoti adresatus" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Sinchronizuoti pranešimus" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Valdyti medijos leistuves" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Valdyti sistemos garsį" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Vykdyti iš anksto nustatytas komandas" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Ir daugiau…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect kartu su GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Siųsti žinutę" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Susirašinėjimai" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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 "Jutiklinis kilimėlis.\n" "Braukite per šią sritį, kad perkeltumėte pelės žymeklį.\n" "Palaikykite nuspaudę, kad perkeltumėte elementus pelės žymekliu.\n\n" "Paprastas spustelėjimas bus siunčiamas į suporuotą įrenginį.\n" "Kairysis, vidurinysis, dešinysis mygtukai ir slinkimas ratuku." #: 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:482 msgid "Desktop" msgstr "Stalinis kompiuteris" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Iškarpinės sinchronizavimas" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Medijos leistuvės" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Pelė ir klaviatūra" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Garsumo reguliavimas" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Failai" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Gauti failus" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Įrašyti failus į" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Bendrinimas" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Įrenginio baterija" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Pranešimas apie žemą baterijos įkrovos lygį" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Pranešimas apie įkrovimą iki tinkinto lygio" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Pranešimas apie pilnai įkrauta bateriją" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Sistemos baterija" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Bendrinti statistiką" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Baterija" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Komandos" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Bendrinimo pranešimai" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Bendrinti, kai aktyvus" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Programos" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Pranešimai" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Adresatai" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Gaunami skambučiai" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Garsumas" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Pristabdyti mediją" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Vykstantys skambučiai" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Nutildyti mikrofoną" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonija" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Veiksmų trumpiniai" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Atstatyti visus…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Trumpiniai" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Įskiepiai" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Eksperimentinis" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Įrenginio podėlis" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Išvalyti podėlį…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Pasenusių SMS palaikymas" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Automatinis SFTP prijungimas" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Išplėstiniai" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Klaviatūros trumpiniai" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Įrenginio nustatymai" #. 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:367 msgid "Pair" msgstr "Suporuoti" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Įrenginys yra nesuporuotas" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Šifravimo informacija" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Panaikinti suporavimą" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Į įrenginį" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Iš įrenginio" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Nieko" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Atkurti" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Sumažinti" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Aptikimas išjungtas" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Siųsti %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 "Siųsti į mobilųjį įrenginį" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Sinchronizuoti tarp įrenginių" #: src/extension.js:159 #, 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:669 src/preferences/device.js:675 msgid "Edit" msgstr "Taisyti" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Šalinti" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Išjungta" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s jau yra naudojamas" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Pilnas KDE Connect įgyvendinimas, skirtas GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Moo" #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Peržiūrėti žurnalą" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Nešiojamas kompiuteris" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Išmanusis telefonas" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Planšetė" #: src/preferences/service.js:480 msgid "Television" msgstr "Televizija" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Nesuporuotas" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Atjungtas" #: src/preferences/service.js:510 msgid "Connected" msgstr "Prijungtas" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Laukiama paslaugos…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Spustelėkite, norėdami šalinti nesklandumus" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Spustelėkite išsamesnei informacijai" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Rinkti numerį" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Bendrinti failą" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Išvardyti prieinamus įrenginius" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Išvardyti visus įrenginius" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Paskirties įrenginys" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Pagrindinė žinutės dalis" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Siųsti pranešimą" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Pranešimo programėlės pavadinimas" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Pagrindinė pranešimo dalis" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Pranešimo piktograma" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Pranešimo ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ryšio patikrinimas" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Rasti telefoną" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Bendrinti nuorodą" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Bendrinti tekstą" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Rodyti laidos versiją" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Patvirtinimo raktas: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Suporavimo užklausa nuo %s" #: src/service/device.js:853 msgid "Reject" msgstr "Atmesti" #: src/service/device.js:858 msgid "Accept" msgstr "Priimti" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL nerasta" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Prievadas jau naudojamas" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Apsikeitimas informacija apie bateriją" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Pilnai įkrauta" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% įkrauta" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Baterija baigia išsikrauti" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "Liko %d%%" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Iškarpinė" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Bendrinti iškarpinės turinį" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Išsiųsti iškarpinės turinį" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Gauti iškaprinės turinį" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Gauti prieigą prie suporuotų įrenginių adresatų" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Rasti mano telefoną" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Skambinti į suporuotą įrenginį" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Jutiklinis kilimėlis" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Nuotolinė įvestis" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Abikryptis nuotolinės medijos atkūrimo valdymas" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Nežinoma" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Bendrinti pranešimus su suporuotu įrenginiu" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Atsisakyti pranešimo" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Užverti pranešimą" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Atsakyti į pranešimą" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Aktyvuoti pranešimą" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ryšio patikrinimas: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Pristatymas" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Naudoti suporuotą įrenginį kaip pristatytoją" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Vykdyti komandas" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Naršyti suporuoto įrenginio failų sistemą" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Prijungti" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Atjungti" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s pranešė apie klaidą" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Bendrinti" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Bendrinti failus ir URL adresus tarp įrenginių" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Persiuntimas nepavyko" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s neleidžiama įkelti failų" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Persiunčiamas failas" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Gaunama „%s“ iš %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Persiuntimas sėkmingas" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Gautas „%s“ iš %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Rodyti failo vietą" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Atverti failą" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Nepavyko gauti “%s” iš %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "%s bendrino tekstą" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Išsiųsta „%s“ į %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Nepavyko išsiųsti “%s” į %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Užbaigus, atverti failą" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Siųsti nuorodą į %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Nauja SMS žinutė (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Atsakyti į SMS žinutę" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Bendrinti SMS žinutę" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Sistemos garsumas" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Leisti suporuotam įrenginiui valdyti sistemos garsumą" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio nerasta" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Nutildyti skambutį" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Nežinomas adresatas" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Gaunamas skambutis" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Ką tik" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Vakar・%s" #: src/service/ui/messaging.js:151 #, 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:401 msgid "Not available" msgstr "Neprieinamas" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Grupės žinutė" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Jūs: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Liko %d∶%02d)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Atsakyti" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Paslauga neprieinama" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Atverti naršyklėje" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/meson.build000066400000000000000000000003471477177637400247160ustar00rootroot00000000000000# 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-ea89821/po/nl.po000066400000000000000000001130071477177637400235230ustar00rootroot00000000000000# 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: 2025-01-03 13:38-0500\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: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-implementatie voor GNOME" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:24 msgid "GSConnect Team" msgstr "GSConnect-team" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 #, 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:44 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:48 msgid "Share files, links and text" msgstr "Bestanden, links en tekst te delen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Berichten te versturen en ontvangen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "De klembordinhoud te synchroniseren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Contactpersonen te synchroniseren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Meldingen te synchroniseren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Mediaspelers te bedienen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Het volumeniveau van het systeem aan te passen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Opgegeven opdrachten uit te voeren" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "En nog veel meer…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect in GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Bericht versturen" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Berichten" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Computer" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Klembordsynchronisatie" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Mediaspelers" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Muis en toetsenbord" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Volumebeheer" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Bestanden" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Bestanden ontvangen" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Bestanden opslaan in" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Delen" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Apparaataccu" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Melding bij laag accuniveau" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Melding bij ingesteld accuniveau" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Melding bij 100% accuniveau" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Systeemaccu" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Statistieken delen" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Accu" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Opdrachten" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Meldingen omtrent delen" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Delen indien actief" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Toepassingen" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Meldingen" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contactpersonen" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Inkomende oproepen" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Media onderbreken" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Actieve gesprekken" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Microfoon dempen" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefoon" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Actiesneltoetsen" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Standaardwaarden…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Sneltoetsen" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Plug-ins" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimenteel" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Apparaatcache" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Cache legen…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Verouderde sms-ondersteuning" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "SFTP automatisch aankoppelen" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Geavanceerd" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Sneltoetsen" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Apparaatinstellingen" #. 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:367 msgid "Pair" msgstr "Koppelen" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Apparaat is niet gekoppeld" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Versleutelingsinformatie" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Ontkoppelen" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Naar apparaat" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Van apparaat" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Niets" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Herstellen" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Verlagen" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Bezig met zoeken naar apparaten…" #: data/ui/preferences-window.ui:353 #, fuzzy msgid "Extension Settings" msgstr "Apparaatinstellingen" #: data/ui/preferences-window.ui:390 #, fuzzy msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect in GNOME Shell" #: 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:144 msgid "Discovery Disabled" msgstr "Herkenning uitgeschakeld" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Versturen naar %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 "Versturen naar mobiel apparaat" #: src/extension.js:50 msgid "Sync between your devices" msgstr "" #: src/extension.js:159 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "Verbonden" msgstr[1] "Verbonden" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Bewerken" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Verwijderen" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Uitgeschakeld" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s is al in gebruik" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Een volledige KDE Connect-implementatie voor GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Heimen Stoffels " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Logboek nakijken" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisie" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Ontkoppeld" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Niet verbonden" #: src/preferences/service.js:510 msgid "Connected" msgstr "Verbonden" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Bezig met wachten op dienst…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Klik voor probleemoplossing" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Klik voor meer informatie" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Nummer bellen" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Bestand delen" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Beschikbare apparaten tonen" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Alle apparaten tonen" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Doelapparaat" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Berichtinhoud" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Melding versturen" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Appnaam op melding" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Meldingsinhoud" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Meldingspictogram" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Meldingsid" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Over laten gaan" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Link delen" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Tekst delen" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Uitgaveversie tonen" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, fuzzy, javascript-format msgid "Verification key: %s" msgstr "Meldingen" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Koppelverzoek van %s" #: src/service/device.js:853 msgid "Reject" msgstr "Weigeren" #: src/service/device.js:858 msgid "Accept" msgstr "Accepteren" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL is niet aangetroffen" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Deze poort is al in gebruik" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Accu-informatie uitwisselen" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Volledig opgeladen" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% opgeladen" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: het accuniveau is laag" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% resterend" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Klembord" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Klembordinhoud delen" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Klembord versturen" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Klembord ophalen" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Toegang tot contactpersonen van het gekoppelde apparaat" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Zoek mijn telefoon" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Gekoppeld apparaat laten rinkelen" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Touchpad" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Media twee kanten op bedienen" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Onbekend" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Meldingen delen met het gekoppelde apparaat" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Melding annuleren" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Melding sluiten" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Antwoordmelding" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Melding inschakelen" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Presentatie" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Gekoppeld apparaat laten fungeren als presentatiescherm" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Opdrachten uitvoeren" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Bladeren door het bestandssysteem van het gekoppelde apparaat" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Aankoppelen" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Ontkoppelen" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s: er is een fout opgetreden" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Delen" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Bestanden en url's uitwisselen tussen apparaten" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Overdracht mislukt" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s mag geen bestanden sturen" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Bestandsoverdracht" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Bezig met ontvangen van “%s” van %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Overdracht voltooid" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "“%s” ontvangen van %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Bestand openen" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Ontvangen van “%s” van %s mislukt" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "Tekst van %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "“%s” is verstuurd naar %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Versturen van “%s” naar %s mislukt" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Openen na overdracht" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Link versturen naar %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Nieuwe sms (uri)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "SMS beantwoorden" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "SMS delen" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volumeniveau van systeem" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Laat het gekoppelde apparaat het systeemvolume aanpassen" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio is niet aangetroffen" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Oproep dempen" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Onbekende contactpersoon" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Inkomende oproep" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Zojuist" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Gisteren - %s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuut" msgstr[1] "%d minuten" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Niet beschikbaar" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Groepsbericht" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Ik: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d resterend)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Beantwoorden" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Dienst niet beschikbaar" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Openen in browser" #~ msgid "Camera" #~ msgstr "Camera" #~ msgid "Photo" #~ msgstr "Foto" #~ 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" #~ 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-ea89821/po/nl_BE.po000066400000000000000000001054231477177637400240740ustar00rootroot00000000000000# 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: 2025-01-03 13:38-0500\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:30 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:24 #, fuzzy msgid "GSConnect Team" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 #, fuzzy msgid "Sync contacts" msgstr "Contacten" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 #, fuzzy msgid "Sync notifications" msgstr "Verzendnotificatie" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 #, fuzzy msgid "Control media players" msgstr "Mediaspelers" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 #, fuzzy msgid "Control system volume" msgstr "Systeemvolume" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 #, fuzzy msgid "Send Message" msgstr "Nieuw bericht" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Berichten" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 #, 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:482 msgid "Desktop" msgstr "Desktop" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Klembordsynchronisatie" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Mediaspelers" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Muis en klavier" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Volumebeheer" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Bestanden" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "" #: data/ui/preferences-device-panel.ui:447 #, fuzzy msgid "Save files to" msgstr "Verzend bestanden naar %s" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Deling" #: data/ui/preferences-device-panel.ui:539 #, fuzzy msgid "Device Battery" msgstr "Accu" #: data/ui/preferences-device-panel.ui:590 #, fuzzy msgid "Low Battery Notification" msgstr "Antwoordnotificatie" #: data/ui/preferences-device-panel.ui:649 #, fuzzy msgid "Charged Up to Custom Level Notification" msgstr "Deelnotificaties" #: data/ui/preferences-device-panel.ui:729 #, fuzzy msgid "Fully Charged Notification" msgstr "Deelnotificaties" #: data/ui/preferences-device-panel.ui:783 #, fuzzy msgid "System Battery" msgstr "Accu" #: data/ui/preferences-device-panel.ui:832 #, fuzzy msgid "Share Statistics" msgstr "Deelnotificaties" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Accu" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Commando's" #: data/ui/preferences-device-panel.ui:975 #, 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:1063 msgid "Share Notifications" msgstr "Deelnotificaties" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Apps" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notificaties" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contacten" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Inkomende oproepen" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Pauzeer media" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "In gesprek" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Microfoon toedoen" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonie" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Actie-sneltoetsen" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Herstel alles…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Sneltoetsen" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Invoegtoepassingen" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "" #: data/ui/preferences-device-panel.ui:1951 #, fuzzy msgid "Device Cache" msgstr "Naar GSM" #: 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 "Geavanceerd" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Sneltoetsen" #: data/ui/preferences-device-panel.ui:2464 #, fuzzy msgid "Device Settings" msgstr "GSM-instellingen" #. 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:367 msgid "Pair" msgstr "Koppel aan" #: data/ui/preferences-device-panel.ui:2540 #, fuzzy msgid "Device is unpaired" msgstr "Toestel is niet geconnecteerd" #: 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:390 msgid "Encryption Info" msgstr "Encryptie-info" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Koppel af" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Naar GSM" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Van GSM" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Niets" #. 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 "Verlagen" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "" #: data/ui/preferences-window.ui:353 #, fuzzy msgid "Extension Settings" msgstr "GSM-instellingen" #: 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 "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:144 msgid "Discovery Disabled" msgstr "Herkenning uitgeschakeld" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Verzend naar %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 "Verzend naar GSM" #: src/extension.js:50 msgid "Sync between your devices" msgstr "" #: src/extension.js:159 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "Verbind" msgstr[1] "Verbind" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Uitgeschakeld" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s is al in gebruik" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Volledige KDE Connect-implementatie voor GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Heimen Stoffels" #: 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 "Laptop" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 #, fuzzy msgid "Television" msgstr "Telefonie" #: src/preferences/service.js:502 #, fuzzy msgid "Unpaired" msgstr "Koppel af" #: src/preferences/service.js:506 #, fuzzy msgid "Disconnected" msgstr "Toestel is niet geconnecteerd" #: src/preferences/service.js:510 #, fuzzy msgid "Connected" msgstr "Verbind" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Klik voor probleemoplossing" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Klik voor meer informatie" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Telefoonoproep" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Deel bestand" #: src/service/daemon.js:337 #, fuzzy msgid "List available devices" msgstr "Onbeschikbaar" #: src/service/daemon.js:346 #, fuzzy msgid "List all devices" msgstr "GSM's" #: src/service/daemon.js:355 #, fuzzy msgid "Target Device" msgstr "Naar GSM" #: src/service/daemon.js:397 #, fuzzy msgid "Message Body" msgstr "Nieuw bericht" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Verzendnotificatie" #: src/service/daemon.js:418 #, fuzzy msgid "Notification App Name" msgstr "Notificaties" #: src/service/daemon.js:427 #, fuzzy msgid "Notification Body" msgstr "Notificaties" #: src/service/daemon.js:436 #, fuzzy msgid "Notification Icon" msgstr "Notificaties" #: src/service/daemon.js:445 #, fuzzy msgid "Notification ID" msgstr "Notificaties" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 #, fuzzy msgid "Ring" msgstr "Lokaliseer" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Deel link" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Deel tekst" #: src/service/daemon.js:505 #, fuzzy msgid "Show release version" msgstr "Kies een conversatie" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, fuzzy, javascript-format msgid "Verification key: %s" msgstr "Notificaties" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Koppelaanvraag van %s" #: src/service/device.js:853 msgid "Reject" msgstr "Verwerp" #: src/service/device.js:858 msgid "Accept" msgstr "Accepteer" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:470 #, fuzzy msgid "Port already in use" msgstr "%s is al in gebruik" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Volledig opgeladen" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, fuzzy, javascript-format msgid "%d%% Charged" msgstr "Volledig opgeladen" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batterij is laag" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% resterend" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Klembord" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Verstuur klembord" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Haal klembord op" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Vind mijn telefoon" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Touchpad" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 #, fuzzy msgid "Unknown" msgstr "Onbekend contact" #: src/service/plugins/notification.js:18 #, fuzzy msgid "Share notifications with the paired device" msgstr "Geen GSM-notificaties" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Annuleer notificatie" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Sluit notificatie" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Antwoordnotificatie" #: src/service/plugins/notification.js:64 #, fuzzy msgid "Activate Notification" msgstr "Deelnotificaties" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 #, fuzzy msgid "Presentation" msgstr "Bestanden-integratie" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Voer commando's uit" #: src/service/plugins/runcommand.js:15 msgid "" "Run commands on your paired device or let the device run predefined commands " "on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Koppel aan" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Koppel af" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Deel" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Overzetten niet gelukt" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 #, fuzzy msgid "Transferring File" msgstr "Overzetten niet gelukt" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "“%s” ontvangen van %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Met succes overgezet" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "“%s” ontvangen van %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Open bestand" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Kon “%s” niet ontvangen van %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "“%s” verzonden naar %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Kon “%s” niet verzenden naar %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 #, fuzzy msgid "Open when done" msgstr "Open in browser" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Verzend een link naar %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Nieuwe SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Beantwoord SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Deel SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Systeemvolume" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 #, fuzzy msgid "PulseAudio not found" msgstr "PulseAudio-fout" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Demp oproep" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Onbekend contact" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Inkomende oproep" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Zopas" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Gisteren - %s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuut" msgstr[1] "%d minuten" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Onbeschikbaar" #: src/service/ui/messaging.js:758 #, fuzzy msgid "Group Message" msgstr "Nieuw bericht" #. 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:115 #, 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 "%d%% (berekenen...)" #. 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 tot volledig opgeladen)" #. 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 resterend)" #: src/shell/notification.js:69 #, fuzzy msgid "Reply" msgstr "Beantwoord SMS" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Dienst onbeschikbaar" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 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-ea89821/po/org.gnome.Shell.Extensions.GSConnect.pot000066400000000000000000000714441477177637400322270ustar00rootroot00000000000000# 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: 2025-01-03 13:38-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:24 msgid "GSConnect Team" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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:144 msgid "Discovery Disabled" msgstr "" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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: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] "" msgstr[1] "" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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:175 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "" #: src/service/daemon.js:337 msgid "List available devices" msgstr "" #: src/service/daemon.js:346 msgid "List all devices" msgstr "" #: src/service/daemon.js:355 msgid "Target Device" msgstr "" #: src/service/daemon.js:397 msgid "Message Body" msgstr "" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "" #: src/service/daemon.js:505 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "" #: src/service/device.js:853 msgid "Reject" msgstr "" #: src/service/device.js:858 msgid "Accept" msgstr "" #: src/service/manager.js:145 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "" #: src/service/plugins/runcommand.js:15 msgid "" "Run commands on your paired device or let the device run predefined commands " "on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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:115 #, 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-ea89821/po/pl.po000066400000000000000000001100751477177637400235270ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "Zespół GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Udostępniać pliki, odnośniki i teksty" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Wysyłać i odbierać wiadomości" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Synchronizować zawartość schowka" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Synchronizować kontakty" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Synchronizować powiadomienia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Sterować odtwarzaczami multimedialnymi" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Sterować głośnością systemową" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Wykonywać wcześniej określone polecenia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "I wiele więcej…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect w Powłoce GNOME" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Wysyła wiadomość" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Wiadomości" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Komputer stacjonarny" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Synchronizacja schowka" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Odtwarzacze multimediów" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Mysz i klawiatura" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Sterowanie głośnością" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Pliki" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Odbieranie plików" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Zapisywanie plików w" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Udostępnianie" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Akumulator urządzenia" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Powiadomienie o niskim poziomie naładowania akumulatora" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Powiadomienie o naładowaniu do danego poziomu" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Powiadomienie o pełnym naładowaniu" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Akumulator komputera" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Udostępnianie statystyk" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Akumulator" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Polecenia" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Udostępnianie powiadomień" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Udostępnianie podczas aktywności" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Programy" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Powiadomienia" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontakty" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Połączenia przychodzące" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Głośność" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Wstrzymywanie multimediów" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Trwające połączenia" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Wyciszanie mikrofonu" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Komunikacja" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Skróty działań" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Przywróć wszystko…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Skróty" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Wtyczki" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Eksperymentalne" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Pamięć podręczna urządzenia" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Wyczyść pamięć podręczną…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Obsługa SMS (poprzednia wersja)" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Automatyczne montowanie SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Zaawansowane" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Skróty klawiszowe" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Ustawienia urządzenia" #. 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:367 msgid "Pair" msgstr "Powiąż" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Urządzenie jest niepowiązane" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informacje o szyfrowaniu" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Odwiąż" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Do urządzenia" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Z urządzenia" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Nic" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Przywróć" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Ciszej" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Wykrywanie jest wyłączone" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Wyślij do „%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 "Wyślij na urządzenie mobilne" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Synchronizacja pomiędzy urządzeniami" #: src/extension.js:159 #, 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:669 src/preferences/device.js:675 msgid "Edit" msgstr "Modyfikuje" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Usuwa" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Wyłączone" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s jest już używane" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Adrian Kryński , 2017\n" "Piotr Drąg , 2018-2020\n" "Aviary.pl , 2018-2020" #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Przejrzyj dziennik" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Laptop" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartfon" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Telewizor" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Niepowiązane" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Rozłączone" #: src/preferences/service.js:510 msgid "Connected" msgstr "Połączone" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Oczekiwanie na usługę…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Uzyskaj pomoc w rozwiązaniu problemu" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Więcej informacji" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Zadzwoń" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Udostępnij plik" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Wyświetla listę dostępnych urządzeń" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Wyświetla listę wszystkich urządzeń" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Urządzenie docelowe" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Treść wiadomości" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Wyślij powiadomienie" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Nazwa aplikacji powiadomienia" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Treść powiadomienia" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Ikona powiadomienia" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Identyfikator powiadomienia" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Zadzwoń" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Udostępnij odnośnik" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Udostępnij tekst" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Wyświetla wersję wydania" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Klucz weryfikacji: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Prośba o powiązanie z urządzenia %s" #: src/service/device.js:853 msgid "Reject" msgstr "Odrzuć" #: src/service/device.js:858 msgid "Accept" msgstr "Przyjmij" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "Nie odnaleziono biblioteki OpenSSL" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Port jest już używany" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Wymiana informacji o naładowaniu akumulatora" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "W pełni naładowane" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "Naładowano %d%%" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: poziom naładowania akumulatora jest niski" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "Pozostało %d%%" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Schowek" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Udostępnianie zawartości schowka" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Wysyłanie do schowka" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Odbieranie ze schowka" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Dostęp do kontaktów powiązanego urządzenia" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Znajdź mój telefon" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Dzwonienie na powiązane urządzenie" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Podkładka pod mysz" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Zdalne sterowanie" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Dwukierunkowe zdalne sterowanie odtwarzaniem multimediów" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Nieznany" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Udostępnianie powiadomień powiązanemu urządzeniu" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Anuluj powiadomienie" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Zamknij powiadomienie" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Odpowiedz na powiadomienie" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Aktywuj powiadomienie" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Prezentacja" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Używanie powiązanego urządzenia jako prezentera" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Wykonywanie poleceń" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Przeglądanie systemu plików powiązanego urządzenia" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Zamontuj" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Odmontuj" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "Urządzenie %s zgłosiło błąd" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Udostępnij" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Udostępnianie plików i adresów URL między urządzeniami" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Przesłanie się nie powiodło" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, 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:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Przesyłanie pliku" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Odbieranie „%s” z urządzenia %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Pomyślnie przesłano" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Odebrano „%s” z urządzenia %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Wyświetl położenie pliku" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Otwórz plik" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, 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:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Wysłano „%s” do urządzenia %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Wysłanie „%s” do urządzenia %s się nie powiodło" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Otwarcie po ukończeniu" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Wysyła odnośnik do urządzenia %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Nowy SMS (adres URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Odpowiedz na SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Udostępnij SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Głośność systemu" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "Nie odnaleziono usługi PulseAudio" #: src/service/plugins/telephony.js:16 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:28 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:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Nieznany kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Połączenie przychodzące" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Przed chwilą" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Wczoraj・%s" #: src/service/ui/messaging.js:151 #, 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:401 msgid "Not available" msgstr "Niedostępne" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Wiadomość grupowa" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Ja: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (pozostało: %d∶%02d)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Odpowiedz" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Usługa jest niedostępna" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Otwórz w przeglądarce" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/pt.po000066400000000000000000001063641477177637400235450ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-14 14:52\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:30 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:24 msgid "GSConnect Team" msgstr "Equipa do GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Partilhar ficheiros, ligações e texto" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Enviar e receber mensagens" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Sinconizar conteúdo da área de transferência" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Sincronizar contactos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Sincronizar notificações" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Controlar leitores multimédia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Controlar volume do sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Executar comandos predefinidos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "E mais…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect na GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Enviar mensagem" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Mensagens" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Área de trabalho" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Sincronizar a área de transferência" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Leitores multimédia" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Rato e Teclado" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Controlo de volume" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Ficheiros" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Receber ficheiros" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Guardar ficheiros para" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Partilhar" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Bateria do dispositivo" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Notificação de bateria fraca" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Carregado até à notificação de nível personalizada" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Notificação de carga completa" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Bateria do sistema" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Partilhar estatísticas" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Bateria" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Comandos" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Partilhar notificações" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Partilhar quando ativo" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Aplicações" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notificações" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contactos" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Chamadas recebidas" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Colocar em pausa" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Chamadas em curso" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Silenciar microfone" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonia" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Atalhos de ação" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Repor tudo…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Atalhos" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Extensões" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimental" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Cache do dispositivo" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Limpar cache…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Suporte por SMS antigo" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Montagem automática SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avançado" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Teclas de atalho" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Definições do dispositivo" #. 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:367 msgid "Pair" msgstr "Emparelhar" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "O dispositivo não está emparelhado" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informação de encriptação" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Desemparelhar" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Para o dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Do dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Nada" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Baixo" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Detecção desativada" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "Adicionar dispositivo por IP…" #: 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:402 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:38 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: 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 "Enviar para %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 "Enviar para dispositivo móvel" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Sincronize entre os seus dispositivos" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d ligado" msgstr[1] "%d ligado" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Editar" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Remover" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Desativado" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "O %s já está a ser usado" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Duarte Loreto \n" "Tiago Santos \n" "Pedro Albuquerque \n" "Juliano de Souza Camargo \n" "Hugo Carvalho " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Registo de revisão" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Portátil" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisão" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Desemparelhado" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Desligado" #: src/preferences/service.js:510 msgid "Connected" msgstr "Ligado" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "À espera de serviço…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Clique para ajudar a solucionar problemas" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Clique para mais informação" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Marcar número" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Partilhar ficheiro" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Listar dispositivos disponíveis" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Listar todos os dispositivos" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Para o dispositivo" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Corpo da mensagem" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Enviar notificação" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Nome da aplicação de notificação" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Corpo da notificação" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Ícone de notificação" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID da notificação" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Toque" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Partilhar ligação" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Partilhar texto" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Mostrar versão de lançamento" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Chave de verificação: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Pedido para emparelhar de %s" #: src/service/device.js:853 msgid "Reject" msgstr "Rejeitar" #: src/service/device.js:858 msgid "Accept" msgstr "Aceitar" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL não encontrado" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Porta já em utilização" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Trocar informações de bateria" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Totalmente carregado" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Carregada" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: A bateria está fraca" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% restante" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Área de transferência" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Partilhar o conteúdo da área de transferência" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Enviar da área de transferência" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Carregar da área de transferência" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Aceder a contactos do dispositivo emparelhado" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Encontrar o meu telefone" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Tocar no seu dispositivo emparelhado" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Tapete de rato" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Entrada remota" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Controlo remoto bidirecional de reprodução multimédia" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Desconhecida" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Partilhar notificações com o dispositivo emparelhado" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Cancelar notificação" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Fechar notificação" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Enviar notificação" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Ativar notificações" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Apresentação" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Usar o dispositivo emparelhado como apresentador" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Executar comandos" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Navegar pelo sistema de ficheiros do dispositivo emparelhado" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s relatou um erro" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Partilha" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Partilhar ficheiros e URLs entre dispositivos" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Falha ao transferir" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, 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:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "A transferir ficheiro" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "A receber \"%s\" de %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Transferência bem sucedida" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Recebeu \"%s\" de %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Mostrar localização do ficheiro" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Abrir ficheiro" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Falha ao receber “%s” de %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Enviou \"%s\" para %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Falha ao enviar “%s” para %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Abrir quando terminar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Enviar um ligação para %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Novo SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Responder SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Partilhar SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volume do sistema" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio não encontrado" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Silenciar chamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Contacto desconhecido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Chamada recebida" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Agora mesmo" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Ontem・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Não disponível" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Mensagem para grupo" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Você: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d restante)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Serviço indisponível" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Abrir no navegador" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/pt_BR.po000066400000000000000000001060011477177637400241140ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "Equipe GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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 é uma implementação completa do KDE Connect especialmente para GNOME Shell com integração com o Nautilus, o Chrome e o Firefox. O time do KDE Connect possui aplicativos para Linux, BSD, Android, Sailfish, iOS, macOS e Windows." #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:44 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:48 msgid "Share files, links and text" msgstr "Compartilhar arquivos, links e texto" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Enviar e receber mensagens" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Sincronizar o conteúdo da área de transferência" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Sincronizar contatos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Sincronizar notificações" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Controlar reprodutores de mídia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Controlar o volume do sistema" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Executar comandos predefinidos" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "E mais…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect no GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Enviar mensagem" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Mensagens" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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 "Touchpad.\n" "Arraste o dedo nesta área para mover o ponteiro do mouse.\n" "Aperte e arraste para mover o ponteiro do mouse.\n\n" "Um clique simples será enviado ao aparelho pareado. \n" "Botão da esquerda, meio e direita, e scroll do mouse." #: 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:482 msgid "Desktop" msgstr "Computador" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Sincronização da área de transferência" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Reprodutores de mídia" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Mouse & teclado" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Controle de volume" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Arquivos" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Receber arquivos" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Salvar arquivos em" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Compartilhamento" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Bateria do dispositivo" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Notificação de bateria fraca" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Notificação de nível de carga personalizado" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Notificação de bateria carregada" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Bateria do sistema" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Compartilhar estatísticas" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Bateria" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Comandos" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Compartilhar notificações" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Compartilhar quanto ativo" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Aplicativos" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Notificações" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Contatos" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Chamadas recebidas" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volume" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Pausar mídia" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Chamadas em andamento" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Silenciar microfone" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonia" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Atalhos de ações" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Redefinir tudo…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Atalhos" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Plugins" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimental" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Cache do dispositivo" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Limpar cache…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Suporte a SMS legado" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Automontagem SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avançado" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Atalhos de teclado" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Configurações do dispositivo" #. 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:367 msgid "Pair" msgstr "Parear" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "O dispositivo não está pareado" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informação de criptografia" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Esquecer" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Para o dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Do dispositivo" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Nada" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Baixo" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Procurando dispositivos…" #: data/ui/preferences-window.ui:353 msgid "Extension Settings" msgstr "Configurações de Extensão" #: data/ui/preferences-window.ui:390 msgid "GSConnect remains active when GNOME Shell is locked" msgstr "O GSConnect continua ativo enquanto o GNOME Shell estiver bloqueado" #: 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:144 msgid "Discovery Disabled" msgstr "Descoberta desativada" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Enviar para %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 "Enviar para dispositivo móvel" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Sincronização entre os seus aparelhos" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d conectado" msgstr[1] "%d conectados" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Editar" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Remover" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Desativado" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s já está em uso" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Ricardo Silva Veloso \n" "Rafael Fontenelle " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Consultar log" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Notebook" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Televisão" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Não pareado" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Desconectado" #: src/preferences/service.js:510 msgid "Connected" msgstr "Conectado" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Aguardando serviço…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Clique para ajuda na solução de problemas" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Clique para mais informação" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Ligar para número" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Compartilhar arquivo" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Listar dispositivos disponíveis" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Listar todos dispositivos" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Dispositivo de destino" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Corpo da mensagem" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Enviar notificação" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Nome do aplicativo de notificação" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Corpo da notificação" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Ícone da notificação" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID da notificação" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Tocar" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Compartilhar link" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Compartilhar texto" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Mostrar versão de lançamento" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Chave de verificação: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Convite de pareamento de %s" #: src/service/device.js:853 msgid "Reject" msgstr "Rejeitar" #: src/service/device.js:858 msgid "Accept" msgstr "Aceitar" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL não encontrado" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "A porta já está em uso" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Trocar informações sobre bateria" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Carregado" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Carregada" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: bateria fraca" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% restante" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Área de transferência" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Compartilhar conteúdo da área de transferência" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Enviar para área de transferência" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Pegar da área de transferência" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Acessar contatos do aparelho pareado" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Encontrar meu smartphone" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Tocar no seu dispositivo pareado" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Entrada remota" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Controle remoto de reprodução multimídia bidirecional" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Desconhecido" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Compartilhar notificações com o dispositivo pareado" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Cancelar notificação" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Fechar notificação" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Responder notificação" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Ativar notificação" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Apresentação" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Usar dispositivo conectado como apresentador" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Executar comandos" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Navegar pelos arquivos do dispositivo conectado" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s relatou um erro" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Compartilhar" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Compartilhar arquivos e URLs entre dispositivos" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Transferência falhou" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, 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:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Transferindo arquivo" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Recebendo “%s” de %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Transferência completa" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Recebido “%s” de %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Mostrar localização de arquivo" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Abrir arquivo" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Falha ao receber “%s” de %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Enviado “%s” para %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Falha ao enviar “%s” para %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Abrir quando terminar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Enviar um link para %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Novo SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Responder SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Compartilhar SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Volume do sistema" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio não encontrado" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Silenciar chamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Contato desconhecido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Recebendo chamada" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Agora" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Ontem・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Indisponível" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Mensagem de grupo" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Você: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d restante)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Serviço indisponível" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Abrir no navegador" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/ru.po000066400000000000000000001165461477177637400235530ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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: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:24 msgid "GSConnect Team" msgstr "Команда GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Поделиться файлами, ссылками и текстом" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Отправить и получить сообщения" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Синхронизировать буфер обмена" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Синхронизировать контакты" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Синхронизировать уведомления" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Управлять проигрывателями" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Управлять системной громкостью" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Выполнять заданные команды" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "И другое…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect в GNOME Shell" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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" "Используйте левую, среднюю и правую кнопку мыши, а также колесо прокрутки." #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "Уведомления" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "Информация о шифровании" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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 остается активным, когда рабочий стол заблокирован" #: 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:144 msgid "Discovery Disabled" msgstr "Обнаружение выключено" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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 "Возникла непредвиденная ошибка. Пожалуйста, сообщите о проблеме, по возможности предоставьте дополнительную информацию." #: 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 подключено" msgstr[2] "%d подключено" msgstr[3] "%d подключено" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Изменить" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Удалить" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Отключено" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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 "'Losted' " #: 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:175 msgid "Click for help troubleshooting" msgstr "Нажмите для решения проблем" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Нажмите для получения информации" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Вызвать номер" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Поделиться файлом" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Список доступных устройств" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Список всех устройств" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Целевое устройство" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Текст сообщения" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Отправить" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Имя приложения" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Текст уведомления" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Иконка уведомления" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID уведомления" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Пинг" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Найти" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Поделиться ссылкой" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Поделиться текстом" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Показать версию программы" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Ключ проверки: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Запрос сопряжения от %s" #: src/service/device.js:853 msgid "Reject" msgstr "Отклонить" #: src/service/device.js:858 msgid "Accept" msgstr "Принять" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Обнаружение было выключено из-за количества устройств в этой сети." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "OpenSSL не найден" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Порт уже используется" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Оповещать о состоянии батареи" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Полностью заряжено" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% заряжено" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Аккумулятор разряжен" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% осталось" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Буфер обмена" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Поделиться содержимым буфера обмена" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Отправить Буфер обмена" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Запросить Буфер обмена" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Получить доступ к контактам сопряжённого устройства" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Найти мой смартфон" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Сделать гудок вашим устройством" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Тачпад" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Позволяет сопряжённому устройству удалённо работать мышью и клавиатурой" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Удалённый Ввод" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Двунаправлённое управление воспроизведением" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Неизвестно" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Поделиться уведомлениями с сопряжённым устройством" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Отменить уведомление" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Закрыть уведомление" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Ответить" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Активировать уведомление" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Пинг: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Презентация" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Использовать сопряжённое устройство для презентации" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Отправить Команду" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Выполнить команды на сопряжённом устройстве или позволить ему запустить определённые команды на этом ПК" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Просмотреть файловую систему сопряжённого устройства" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Примонтировать" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Размонтировать" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s сообщил об ошибке" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Поделиться" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Делиться файлами и ссылками между устройствами" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Передача не удалась" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s не разрешено загружать файлы" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Передача файла" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Получение «%s» от %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Передача завершена" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Получен «%s» от %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Показать Расположение Файла" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Открыть файл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Не удалось получить«%s» от %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "Текст получен с %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "Отправка «%s» на %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Не удалось отправить «%s» на %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "Отправить файлы на %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "Открыть при завершении" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Отправить ссылку на %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Отправка и чтение SMS с устройства и получение уведомлений о новых SMS" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Новое сообщение (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Ответить на SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Отправить SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Громкость" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Разрешить сопряжённому устройству управлять громкостью системы" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio не найден" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Отключить звонок" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "Входящий звонок" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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 минуты" msgstr[2] "%d минут" 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] "И %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:115 #, 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 "Отправлять ссылки в веб браузер или по 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-ea89821/po/sk.po000066400000000000000000001135161477177637400235340ustar00rootroot00000000000000# 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: 2025-01-03 13:38-0500\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:30 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:24 msgid "GSConnect Team" msgstr "Tím GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 #, 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:44 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:48 msgid "Share files, links and text" msgstr "Zdieľať súbory, prepojenia a text" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Posielať a prijímať správy" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Synchronizovať obsah schránky" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Synchronizovať kontakty" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Synchronizovať oznámenia" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Ovládať multimediálne prehrávače" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Ovládať systémovú hlasitosť" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Spustiť predpripravené príkazy" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "A viac…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect v GNOME Shelli" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Odoslať správu" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Písanie správ" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Stolný počítač" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Synchronizácia schránky" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Multimediálne prehrávače" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Myš a klávesnica" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Ovládanie hlasitosti" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Súbory" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Prijímať súbory" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Ukladať súbory do" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Zdieľanie" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Stav batérie" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Upozornenie na slabú batériu" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Oznámenie o nabití na nastavenú úroveň" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Oznámenie o plnom nabití" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Systémová batéria" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Zdieľať štatistiky" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Batéria" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Príkazy" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Zdieľať oznámenia" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Zdieľať, keď je aktívny" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Aplikácie" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Oznámenia" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontakty" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Prichádzajúce hovory" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Hlasitosť" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Pozastaviť multimédiá" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Odchádzajúce hovory" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Stíšiť mikrofón" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefonovanie" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Skratky akcií" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Obnoviť všetko…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Skratky" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Zásuvné moduly" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimentálne" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Vyrovnávacia pamäť zariadenia" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Vyčistiť vyrovnávaciu pamäť…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Podpora SMS (zastarané)" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "Automatické pripojenie SFTP" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Pokročilé" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Klávesové skratky" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Nastavenie zariadenia" #. 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:367 msgid "Pair" msgstr "Spárovať" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Spárovanie so zariadením bolo zrušené" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Informácie o šifrovaní" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Zrušiť párovanie" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Do zariadenia" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Zo zariadenia" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Bez zmeny" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Obnoviť" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Znížiť" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Hľadajú sa zariadenia…" #: data/ui/preferences-window.ui:353 #, fuzzy msgid "Extension Settings" msgstr "Nastavenie zariadenia" #: data/ui/preferences-window.ui:390 #, fuzzy msgid "GSConnect remains active when GNOME Shell is locked" msgstr "GSConnect v GNOME Shelli" #: 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:144 msgid "Discovery Disabled" msgstr "Objavenie zakázané" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Odoslať na číslo %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 "Odoslať do mobilného zariadenia" #: src/extension.js:50 msgid "Sync between your devices" msgstr "" #: src/extension.js:159 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "Pripojené" msgstr[1] "Pripojené" msgstr[2] "Pripojené" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Upraviť" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Odstrániť" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Zakázané" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "Klávesová skratka %s sa už používa" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Dušan Kazik , Jose Riha " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Prezrieť súbor so záznamom" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Notebook" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Televízia" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Spárovanie zrušené" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Odpojené" #: src/preferences/service.js:510 msgid "Connected" msgstr "Pripojené" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Čaká sa na službu…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Kliknutím získate pomoc pri riešení problému" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Kliknutím získate viac informácií" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Vytočiť číslo" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Zdieľať súbor" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Zobraziť dostupné zariadenia" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Zobraziť všetky zariadenia" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Cieľové zariadenie" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Telo správy" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Odoslať oznámenie" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Meno aplikácie v oznámení" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Telo oznámenia" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Ikona oznámenia" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID oznámenia" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Prezvoniť" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Zdieľať odkaz" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Zdieľať text" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Zobraziť verziu vydania" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, fuzzy, javascript-format msgid "Verification key: %s" msgstr "Oznámenia" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Požiadavka na spárovanie od zariadenia %s" #: src/service/device.js:853 msgid "Reject" msgstr "Odmietnuť" #: src/service/device.js:858 msgid "Accept" msgstr "Prijať" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL nebolo nájdené" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Port už sa používa" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Vymieňať informácie o batérii" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Plne nabitá" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% nabité" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Batéria je takmer vybitá" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "Zostáva %d%%" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Schránka" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Zdieľať obsah schránky" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Odoslať obsah schránky" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Prijať obsah schránky" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Pristupovať ku kontaktom na spárovanom zariadení" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Nájdi môj telefón" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Prezvoní vaše spárované zariadenie" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Ovládanie myši" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Obojsmerné diaľkové ovládanie prehrávania multimédií" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Neznáme" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Zdieľať oznámenia so spárovaným zariadením" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Zrušiť oznámenie" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Zavrieť oznámenie" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Odpovedať na oznámenie" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Aktivovať oznámenia" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Prezentácia" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Použiť spárované zariadenie ako prezentátor" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Spustiť príkazy" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Prehliadať súborový systém spárovaného zariadenia" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Pripojiť" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Odpojiť" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s nahlásilo chybu" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Zdieľať" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Zdieľať súbory a URL adresy medzi zariadeniami" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Prenos zlyhal" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s nemá povolené nahrávanie súborov" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Prebieha prenos súboru" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Prijíma sa „%s“ zo zariadenia %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Prenos úspešný" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Prijatý súbor „%s“ zo zariadenia %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Otvoriť súbor" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Zlyhalo prijatie súboru „%s“ zo zariadenia %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Odoslaný súbor „%s“ do zariadenia %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Zlyhalo odoslanie súboru „%s“ do zariadenia %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Otvoriť po dokončení" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Odošle odkaz do zariadenia %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Nová SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Odpovedať na SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Zdieľať SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Systémová hlasitosť" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio nebolo nájdené" #: src/service/plugins/telephony.js:16 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:28 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:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Neznámy kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Prichádzajúci hovor" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Práve teraz" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Včera・%s" #: src/service/ui/messaging.js:151 #, 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:401 msgid "Not available" msgstr "Nedostupný" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Skupinová správa" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Vy: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Zostáva %d∶%02d)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Odpovedať" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Služba nedostupná" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Otvoriť v prehliadači" #~ msgid "Camera" #~ msgstr "Fotoaparát" #~ msgid "Photo" #~ msgstr "Fotografia" #~ 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" #~ 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-ea89821/po/sr.po000066400000000000000000001002161477177637400235340ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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: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:24 msgid "GSConnect Team" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "Обавештења" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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:144 msgid "Discovery Disabled" msgstr "Откривање је онемогућено" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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 "Пошаљи на %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] "" msgstr[1] "" msgstr[2] "" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Онемогућен" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s је спреман за употребу" #: 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 "Слободан Терзић (githzerai06@gmail.com)" #: 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:175 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Бирај број" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Дели фајл" #: src/service/daemon.js:337 msgid "List available devices" msgstr "" #: src/service/daemon.js:346 msgid "List all devices" msgstr "" #: src/service/daemon.js:355 msgid "Target Device" msgstr "" #: src/service/daemon.js:397 msgid "Message Body" msgstr "" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Пошаљи обавештење" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Пинг" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Подели везу" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Дели текст" #: src/service/daemon.js:505 msgid "Show release version" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Захтев за упаривање од %s" #: src/service/device.js:853 msgid "Reject" msgstr "Одбиј" #: src/service/device.js:858 msgid "Accept" msgstr "Прихвати" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Откривање је онемогућено услед броја уређаја у овој мрежи." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Потпуно пуна" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: батерија је при крају" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% преостаје" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Остава" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Слање у оставу" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Довлачење из оставе" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Нађи ми телефон" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Подлога за миша" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "МПРИС" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Откажи обавештење" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Затвори обавештење" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Обавештење о оддговору" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Пинг: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Извршавање нареби" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "СФТП" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Монтирај" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Демонтирај" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Дељење" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Пренос није успео" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Примам „%s“ од %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Успешан пренос" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Примих „%s“ од %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Отвори фајл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Неуспешан пријем „%s“ од %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "Дељени текст од %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "Слање „%s“ за %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Неуспело слање „%s“ на %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "Пошаљи фајлове на %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Пошаљи везе на %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "СМС" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Нови СМС (УРИ)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Одговори на СМС" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Дели СМС" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Системска јачина" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Утишај позив" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "Долазни позив" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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] "" #: 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] "" msgstr[2] "" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:115 #, 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 "%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 "Дели везе ГСКонектом, директно у преглдач или путем СМС-а." #. 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-ea89821/po/sr@latin.po000066400000000000000000001002401477177637400246610ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Nema kontakata" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Pošalji obaveštenje" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Medijski plejeri" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Sistemska jačina" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Pošalji poruku" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Poruke" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Radna površ" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Sinhronizacija ostave" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Medijski plejeri" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Miš i tastatura" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Jačina zvuka" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Fajlovi" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Primi fajlove" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Pošalji fajlove na" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Deljenje" #: 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "Обавештења" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Tražim uređaje…" #: 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 "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:144 msgid "Discovery Disabled" msgstr "Otkrivanje je onemogućeno" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Pošalji na mobilni uređaj" #: 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] "" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Uredi" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Ukloni" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Onemogućen" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s je spreman za upotrebu" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "Potpuna implementacija KDE Konekta za Gnom" #. TRANSLATORS: eg. 'Translator Name ' #: src/preferences/service.js:370 msgid "translator-credits" msgstr "Slobodan Terzić (githzerai06@gmail.com)" #: 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 "Pametni telefon" #: 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 "Nepovezan" #: src/preferences/service.js:510 msgid "Connected" msgstr "Povezan" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Čekam na servis…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Kliknite za pomoć u otklanjanju" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Kliknite za više informacija" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Biraj broj" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Deli fajl" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Nije dostupno" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Mobilni uređaji" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Na uređaj" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Tijelo poruke" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Pošalji obaveštenje" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Obaveštenja" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID broj obaveštenja" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Pozvoni" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Podeli vezu" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Deli tekst" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Dodajte osobe da započnete razgovor" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Zahtev za uparivanje od %s" #: src/service/device.js:853 msgid "Reject" msgstr "Odbij" #: src/service/device.js:858 msgid "Accept" msgstr "Prihvati" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "%s je spreman za upotrebu" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Potpuno puna" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: baterija je pri kraju" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% preostaje" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Ostava" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Слање у оставу" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Довлачење из оставе" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Нађи ми телефон" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Подлога за миша" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "МПРИС" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Откажи обавештење" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Затвори обавештење" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Обавештење о оддговору" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Извршавање нареби" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "СФТП" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Монтирај" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Демонтирај" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Дељење" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Пренос није успео" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Примам „%s“ од %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Успешан пренос" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Примих „%s“ од %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Отвори фајл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Неуспешан пријем „%s“ од %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "Дељени текст од %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "Слање „%s“ за %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Неуспело слање „%s“ на %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "Пошаљи фајлове на %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Пошаљи везе на %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "СМС" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Нови СМС (УРИ)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Одговори на СМС" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Дели СМС" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Системска јачина" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Утишај позив" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "Долазни позив" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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] "" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Nije dostupno" #: 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] "" msgstr[2] "" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:115 #, 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 "%d%% (Procenjujem…)" #. 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 do pune)" #. 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 preostaje)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Odgovori" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Servis nije dostupan" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Otvori u pregledaču" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/sv.po000066400000000000000000001036511477177637400235460ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "GSConnect-gruppen" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Dela filer, länkar och text" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Skicka och ta emot meddelanden" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Synkronisera urklippets innehåll" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Synkronisera kontakter" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Synkronisera aviseringar" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Styr mediaspelare" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Styr systemets volym" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Kör fördefinierade kommandon" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Och mer…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect i GNOME Shell" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Skicka meddelande" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Meddelanden" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Skrivbord" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Urklippssynkronisering" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Mediaspelare" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Mus & tangentbord" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Volymkontroll" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Filer" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Ta emot filer" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Spara filer till" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Delning" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Batteri för enhet" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Avisering vid låg batterinivå" #: data/ui/preferences-device-panel.ui:649 msgid "Charged Up to Custom Level Notification" msgstr "Avisering när uppladdning når inställd nivå" #: data/ui/preferences-device-panel.ui:729 msgid "Fully Charged Notification" msgstr "Avisering när fulladdad" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Systemets batteri" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "Dela statistik" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Batteri" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Kommandon" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Dela aviseringar" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Dela när aktiv" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Program" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Aviseringar" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kontakter" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Inkommande samtal" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Volym" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Pausa media" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Pågående samtal" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Stäng av mikrofonen" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefon" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Genvägar för åtgärd" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Återställ alla…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Genvägar" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Insticksmoduler" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Experimentellt" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Enhetscache" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Rensa cache…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Äldre SMS-stöd" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "SFTP-automontering" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Avancerat" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Tangentbordsgenvägar" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Enhetsinställningar" #. 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:367 msgid "Pair" msgstr "Para ihop" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Enheten är oparad" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Krypteringsinformation" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Ta bort ihopparning" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Till enhet" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Från enhet" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Inget" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Återställ" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Sänk" #. 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:197 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:114 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:644 msgid "Searching for devices…" msgstr "Söker efter enheter…" #: 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 "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:144 msgid "Discovery Disabled" msgstr "Upptäckt inaktiverat" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Skicka till %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 "Skicka till mobil enhet" #: 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 ansluten" msgstr[1] "%d anslutna" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Redigera" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Ta bort" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Inaktiverad" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s används redan" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Morgan Antonsson " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Granska logg" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Bärbar dator" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Smartphone" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Surfplatta" #: src/preferences/service.js:480 msgid "Television" msgstr "TV" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Inte ihopparad" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Bortkopplad" #: src/preferences/service.js:510 msgid "Connected" msgstr "Ansluten" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Väntar på tjänst…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Klicka för att få hjälp med felsökning" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Klicka här för mer information" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Slå nummer" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Dela fil" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Visa tillgängliga enheter" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Visa alla enheter" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Målenhet" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Meddelande-text" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Skicka avisering" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Namn på aviseringsapp" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Aviseringstext" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Aviseringsikon" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Aviserings-ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Ring" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Dela länk" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Dela text" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Visa utgåvans version" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Begäran om ihopparning från %s" #: src/service/device.js:853 msgid "Reject" msgstr "Avvisa" #: src/service/device.js:858 msgid "Accept" msgstr "Acceptera" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL kunde inte hittas" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Porten används redan" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Utbyt batteriinformation" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Fulladdad" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% laddat" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Låg batterinivå" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% kvar" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Urklipp" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Dela urklippets innehåll" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Skicka urklipp" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Hämta urklipp" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Få tillgång till kontakter från den parkopplade enheten" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Hitta min telefon" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Ring din ihopparade enhet" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Musmatta" #: src/service/plugins/mousepad.js:15 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:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Dubbelriktad fjärrstyrning för medieuppspelning" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Okänd" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Dela aviseringar med den ihopparade enheten" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Avbryt avisering" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Stäng avisering" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Svara på avisering" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Aktivera avisering" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Presentation" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Använd den ihopparade enheten som presentatör" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Kör kommandon" #: src/service/plugins/runcommand.js:15 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:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Bläddra i den ihopparade enhetens filsystem" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Montera" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Avmontera" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s rapporterade ett felmeddelande" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Dela" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Dela filer och webbadresser mellan enheter" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Överföring misslyckades" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s får inte ladda upp filer" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Överför fil" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Tar emot ”%s” från %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Överföringen klar" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Tog emot ”%s” från %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Öppna fil" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, 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:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Skickade ”%s” till %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Det gick inte att skicka ”%s” till %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Öppna när det är klart" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Skicka en länk till %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 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:38 msgid "New SMS (URI)" msgstr "Nytt SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Svara SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Dela SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Systemvolym" #: src/service/plugins/systemvolume.js:14 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:58 msgid "PulseAudio not found" msgstr "PulseAudio kunde inte hittas" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Tysta samtal" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Okänd kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Inkommande samtal" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Precis nu" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Igår・%s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minuter" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Inte tillgänglig" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Gruppmeddelande" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Du: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d kvar)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Svara" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Tjänsten är inte tillgänglig" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Öppna i webbläsare" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/tr.po000066400000000000000000001032111477177637400235330ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 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:24 msgid "GSConnect Team" msgstr "GSConnect Takımı" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Dosyaları, bağlantıları ve metni paylaş" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Mesaj gönder ve al" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Pano içeriğini eşitle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Kişileri eşitle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Bildirimleri eşitle" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Medya oynatıcıları kontrol et" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Sistem sesini kontrol et" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Ön tanımlı komutları yürüt" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Ve daha fazlası…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GNOME Kabuğunda GSConnect" #: data/ui/connect-dialog.ui:20 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:405 src/service/plugins/share.js:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: webextension/gettext.js:42 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:429 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:54 msgid "Send Message" msgstr "Mesaj Gönder" #: data/ui/messaging-conversation.ui:92 src/shell/notification.js:84 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:30 #: src/service/ui/messaging.js:1057 msgid "Messaging" msgstr "Mesajlaşma" #: data/ui/messaging-window.ui:30 data/ui/messaging-window.ui:43 #: src/service/ui/messaging.js:1268 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:482 msgid "Desktop" msgstr "Masaüstü" #: data/ui/preferences-device-panel.ui:102 msgid "Clipboard Sync" msgstr "Pano Eşleşmesi" #: data/ui/preferences-device-panel.ui:168 msgid "Media Players" msgstr "Medya Oynatıcılar" #: data/ui/preferences-device-panel.ui:225 msgid "Mouse & Keyboard" msgstr "Fare & Klavye" #: data/ui/preferences-device-panel.ui:282 msgid "Volume Control" msgstr "Ses Kontrolü" #: data/ui/preferences-device-panel.ui:336 src/service/plugins/sftp.js:331 msgid "Files" msgstr "Dosyalar" #: data/ui/preferences-device-panel.ui:388 msgid "Receive Files" msgstr "Alınan Dosyalar" #: data/ui/preferences-device-panel.ui:447 msgid "Save files to" msgstr "Dosyaları şuraya kaydet" #: data/ui/preferences-device-panel.ui:508 #: data/ui/preferences-device-panel.ui:2170 msgid "Sharing" msgstr "Paylaşım" #: data/ui/preferences-device-panel.ui:539 msgid "Device Battery" msgstr "Cihaz Bataryası" #: data/ui/preferences-device-panel.ui:590 msgid "Low Battery Notification" msgstr "Düşük Batarya Bildirimi" #: 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 "Tam Şarj Bildirimi" #: data/ui/preferences-device-panel.ui:783 msgid "System Battery" msgstr "Sistem Bataryası" #: data/ui/preferences-device-panel.ui:832 msgid "Share Statistics" msgstr "İstatistikleri Paylaş" #: data/ui/preferences-device-panel.ui:886 #: data/ui/preferences-device-panel.ui:2216 src/service/plugins/battery.js:14 msgid "Battery" msgstr "Batarya" #: 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 msgid "Commands" msgstr "Komutlar" #: data/ui/preferences-device-panel.ui:975 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:1063 msgid "Share Notifications" msgstr "Bildirimleri Paylaş" #: data/ui/preferences-device-panel.ui:1123 msgid "Share When Active" msgstr "Aktif Olduğunda Paylaş" #: data/ui/preferences-device-panel.ui:1174 msgid "Applications" msgstr "Uygulamalar" #: data/ui/preferences-device-panel.ui:1220 #: data/ui/preferences-device-panel.ui:2308 #: src/service/plugins/notification.js:17 msgid "Notifications" msgstr "Bildirimler" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 msgid "Contacts" msgstr "Kişiler" #: data/ui/preferences-device-panel.ui:1331 msgid "Incoming Calls" msgstr "Gelen Çağrılar" #: data/ui/preferences-device-panel.ui:1380 #: data/ui/preferences-device-panel.ui:1547 msgid "Volume" msgstr "Ses" #: data/ui/preferences-device-panel.ui:1446 #: data/ui/preferences-device-panel.ui:1613 msgid "Pause Media" msgstr "Medyayı Duraklat" #: data/ui/preferences-device-panel.ui:1499 msgid "Ongoing Calls" msgstr "Devam Eden Çağrılar" #: data/ui/preferences-device-panel.ui:1669 msgid "Mute Microphone" msgstr "Mikrofonu Sustur" #: data/ui/preferences-device-panel.ui:1723 #: data/ui/preferences-device-panel.ui:2354 src/service/plugins/telephony.js:15 msgid "Telephony" msgstr "Telefon" #: data/ui/preferences-device-panel.ui:1758 msgid "Action Shortcuts" msgstr "Kısayol Eylemleri" #: data/ui/preferences-device-panel.ui:1774 msgid "Reset All…" msgstr "Tümünü Sıfırla…" #: data/ui/preferences-device-panel.ui:1826 msgid "Shortcuts" msgstr "Kısayollar" #: data/ui/preferences-device-panel.ui:1857 msgid "Plugins" msgstr "Eklentiler" #: data/ui/preferences-device-panel.ui:1904 msgid "Experimental" msgstr "Deneysel" #: data/ui/preferences-device-panel.ui:1951 msgid "Device Cache" msgstr "Cihaz Önbelleği" #: data/ui/preferences-device-panel.ui:1969 msgid "Clear Cache…" msgstr "Önbelleği Temizle…" #: data/ui/preferences-device-panel.ui:2008 msgid "Legacy SMS Support" msgstr "Eski SMS Desteği" #: data/ui/preferences-device-panel.ui:2065 msgid "SFTP Automount" msgstr "SFTP Otomatik Bağla" #: data/ui/preferences-device-panel.ui:2120 #: data/ui/preferences-device-panel.ui:2446 msgid "Advanced" msgstr "Gelişmiş" #: data/ui/preferences-device-panel.ui:2400 msgid "Keyboard Shortcuts" msgstr "Klavye Kısayolları" #: data/ui/preferences-device-panel.ui:2464 msgid "Device Settings" msgstr "Cihaz Ayarları" #. 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:367 msgid "Pair" msgstr "Eşleştir" #: data/ui/preferences-device-panel.ui:2540 msgid "Device is unpaired" msgstr "Cihaz eşleşmemiş" #: data/ui/preferences-device-panel.ui:2555 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:2595 src/preferences/device.js:390 msgid "Encryption Info" msgstr "Şifreleme Bilgisi" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 msgid "Unpair" msgstr "Eşleştirmeyi Bitir" #. TRANSLATORS: Send clipboard content to device #: data/ui/preferences-device-panel.ui:2618 msgid "To Device" msgstr "Cihaza" #. TRANSLATORS: Receive clipboard content from the device #: data/ui/preferences-device-panel.ui:2624 msgid "From Device" msgstr "Cihazdan" #. TRANSLATORS: Don't change the system volume #: data/ui/preferences-device-panel.ui:2636 #: data/ui/preferences-device-panel.ui:2669 msgid "Nothing" msgstr "Hiç Biri" #. TRANSLATORS: Restore the system volume #: data/ui/preferences-device-panel.ui:2643 #: data/ui/preferences-device-panel.ui:2676 msgid "Restore" msgstr "Geri yükle" #. TRANSLATORS: Lower the system volume #: data/ui/preferences-device-panel.ui:2650 #: data/ui/preferences-device-panel.ui:2683 msgid "Lower" msgstr "Düşük" #. 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:197 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:114 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:644 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:390 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:144 msgid "Discovery Disabled" msgstr "Keşif Devre Dışı" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "" #: 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:402 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:38 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: 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 "Gönder %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 "Mobil Cihaza Gönder" #: src/extension.js:50 msgid "Sync between your devices" msgstr "Cihazlarınız arasında eşitleyin" #: src/extension.js:159 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d Bağlı" msgstr[1] "%d Bağlı" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Düzenle" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Kaldır" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Devre dışı" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "%s zaten kullanılıyor" #: src/preferences/service.js:361 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:370 msgid "translator-credits" msgstr "Serdar Sağlam \n" "Orhan Engin Okay \n" "A. Burak Tektaş " #: src/preferences/service.js:403 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:406 msgid "Review Log" msgstr "Günlüğü Gözden Geçir" #: src/preferences/service.js:474 msgid "Laptop" msgstr "Dizüstü" #: src/preferences/service.js:476 msgid "Smartphone" msgstr "Telefon" #: src/preferences/service.js:478 msgid "Tablet" msgstr "Tablet" #: src/preferences/service.js:480 msgid "Television" msgstr "Televizyon" #: src/preferences/service.js:502 msgid "Unpaired" msgstr "Eşleşmemiş" #: src/preferences/service.js:506 msgid "Disconnected" msgstr "Bağlantı kesildi" #: src/preferences/service.js:510 msgid "Connected" msgstr "Bağlı" #: src/preferences/service.js:646 msgid "Waiting for service…" msgstr "Hizmet bekleniyor…" #: src/service/daemon.js:175 msgid "Click for help troubleshooting" msgstr "Sorun giderme konusunda yardım için tıkla" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Daha fazla bilgi için tıkla" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Numarayı Çevir" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Dosya Paylaşımı" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Kullanılabilir cihazları listele" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Tüm cihazları listele" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Hedef Cihaz" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Mesaj Metni" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Bildirim Gönder" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Bildirim Uygulama Adı" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Bildirim Metni" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Bildirim Simgesi" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "Bildirim Kimliği" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Yokla" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Çaldır" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Bağlantıyı Paylaş" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Metni Paylaş" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Sürüm versiyonunu göster" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Doğrulama anahtarı: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "%s tarafından Gelen Eşleşme İsteği" #: src/service/device.js:853 msgid "Reject" msgstr "Reddet" #: src/service/device.js:858 msgid "Accept" msgstr "Kabul et" #: src/service/manager.js:145 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:177 msgid "OpenSSL not found" msgstr "OpenSSL bulunamadı" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Bağlantı noktası zaten kullanılıyor" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Şarj Oldu" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Düşük batarya" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "%d%% kaldı" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Pano" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Panoya Gönder" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Panodan Al" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Telefonumu Bul" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Fare Desteği" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Uzak Girdi" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Bilinmeyen" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Bildirimi İptal Et" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Bildirimi Kapat" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Bildirimi Yanıtla" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Bildirimi Etkinleştir" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Yokla: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Sunum" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Komutları Çalıştır" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Bağla" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Ayır" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s bir hata bildirdi" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Paylaş" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Aktarım Başarısız" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s dosya yükleme iznine sahip değil" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Dosya Aktarımı" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Alınıyor “%s” kaynak %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Aktarım Başarılı" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Alınan “%s” kaynak %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Dosya Konumunu Göster" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Dosyayı Aç" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Alım başarısız “%s” kaynak %s" #: src/service/plugins/share.js:242 #, 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:292 #, 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:312 #, javascript-format msgid "Sent “%s” to %s" msgstr "Gönderildi “%s” hedef %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Gönderilemedi “%s” hedef %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, 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:384 msgid "Open when done" msgstr "Tamamlandığında aç" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Bağlantı gönder %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Yeni SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "SMS Yanıtla" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "SMS Paylaş" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Sistem Sesi" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio bulunamadı" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Sesi Kapat" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: src/service/ui/contacts.js:611 src/service/ui/messaging.js:750 msgid "Unknown Contact" msgstr "Bilinmeyen Kişi" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:193 msgid "Incoming call" msgstr "Gelen çağrı" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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: Less than a minute ago #: src/service/ui/messaging.js:105 src/service/ui/messaging.js:146 msgid "Just now" msgstr "Hemen şimdi" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:114 #, javascript-format msgid "Yesterday・%s" msgstr "Dün %s" #: src/service/ui/messaging.js:151 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d dakika" msgstr[1] "%d dakika" #: src/service/ui/messaging.js:401 msgid "Not available" msgstr "Müsait Değil" #: src/service/ui/messaging.js:758 msgid "Group Message" msgstr "Grup Mesajı" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:773 #, javascript-format msgid "You: %s" msgstr "Sen: %s" #: src/service/ui/messaging.js:959 #, 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:115 #, 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:124 #, 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:133 #, 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:141 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Kalan Süre %d∶%02d)" #: src/shell/notification.js:69 msgid "Reply" msgstr "Yanıtla" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:32 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:36 msgid "Service Unavailable" msgstr "Servis Mevcut Değil" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:40 msgid "Open in Browser" msgstr "Tarayıcıda Aç" GSConnect-gnome-shell-extension-gsconnect-ea89821/po/uk.po000066400000000000000000001176701477177637400235430ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-19 19:41\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: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:24 msgid "GSConnect Team" msgstr "Команда GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "Ділитися файлами, посиланнями і текстом" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "Надсилати й отримувати повідомлення" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "Синхронізувати вміст буфера обміну" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "Синхронізувати контакти" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "Синхронізувати сповіщення" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "Керувати медіа плеєрами" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "Керувати гучністю системи" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "Виконувати попередньо визначені команди" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "Та інше…" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GSConnect у GNOME Shell" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "Сповіщення" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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:367 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:390 msgid "Encryption Info" msgstr "Дані щодо шифрування" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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 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:144 msgid "Discovery Disabled" msgstr "Видимість вимкнено" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "Додати IP пристрою…" #: 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 під'єднано" msgstr[2] "%d під'єднано" msgstr[3] "%d під'єднано" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "Редагувати" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "Видалити" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "Вимкнено" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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 "kotyhoroshko" #: 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:175 msgid "Click for help troubleshooting" msgstr "Натисніть, щоб допомогти усунути проблему" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "Натисніть для отримання додаткової інформації" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "Набрати номер" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "Поділитися файлом" #: src/service/daemon.js:337 msgid "List available devices" msgstr "Перелічити доступні пристрої" #: src/service/daemon.js:346 msgid "List all devices" msgstr "Перелічити всі пристрої" #: src/service/daemon.js:355 msgid "Target Device" msgstr "Цільовий пристрій" #: src/service/daemon.js:397 msgid "Message Body" msgstr "Тіло повідомлення" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "Відправити сповіщення" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "Назва застосунку для сповіщень" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "Тіло сповіщення" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "Піктограма сповіщення" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "ID сповіщення" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Пінг" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "Дзвеніти" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "Поділитися посиланням" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "Поділитися текстом" #: src/service/daemon.js:505 msgid "Show release version" msgstr "Показати версію програми" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, 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:212 #, javascript-format msgid "Verification key: %s" msgstr "Ключ перевірки: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "Запит на пов'язування від %s" #: src/service/device.js:853 msgid "Reject" msgstr "Відхилити" #: src/service/device.js:858 msgid "Accept" msgstr "Прийняти" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Видимість було вимкнено через велику кількість пристроїв у цій мережі." #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "OpenSSL не знайдено" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "Порт уже використовується" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "Обмін інформацією про акумулятор" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "Повністю заряджений" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% Заряджено" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Низький заряд акумулятора" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "залишилось %d%%" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "Буфер обміну" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "Надсилати вміст буфера обміну" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "Поміщати у буфер обміну" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "Отримувати з буферу обміну" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "Доступ до контактів повʼязаного пристрою" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "Знайти мій телефон" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "Дзвеніти повʼязаним пристроєм" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Тачпад" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "Дозволяє пов'язаному пристрою працювати віддаленою мишею та клавіатурою" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "Віддалене введення" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "Двостороннє віддалене керування відтворенням медіа" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "Невідомо" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "Ділитися сповіщеннями з повʼязаним пристроєм" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "Скасувати сповіщення" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "Закрити сповіщення" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "Відповісти на сповіщення" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "Активувати сповіщення" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Пінг: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "Презентація" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "Використовуйте спарений пристрій презентатором" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "Виконати команди" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "Виконуйте команди на вашому повʼязаному пристрої або дозвольте пристрою виконувати попередньо визначені команди на цьому ПК" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "Перегляд файлової системи пов'язаного пристрою" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "Змонтувати" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "Демонтувати" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s повідомляє про помилку" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "Поділитись" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "Діліться файлами й URL-адресами між пристроями" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "Помилка передачі" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s не має дозволу вивантажувати файли" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "Передавання файлу" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Отримання «%s» від %s" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "Передача успішна" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "Отримано «%s» від %s" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "Показати розташування файлу" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "Відкрити файл" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Не вдалося отримати «%s» від %s" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "Текст " #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "Відправлення «%s» до %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Не вдалося відправити «%s» до %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "Відправити файли до %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "Відкрити після завершення" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "Відправити посилання до %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "Надсилайте і читайте SMS вашого повʼязаного пристрою та отримуйте сповіщення про нові SMS" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "Нове SMS (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "Відповісти на SMS" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "Поділитись SMS" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "Гучність системи" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "Увімкніть керування системною гучністю повʼязаного пристрою" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "PulseAudio не знайдено" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "Приглушити дзвінок" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "Вхідний дзвінок" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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 хвилини" msgstr[2] "%d хвилин" 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] "І ще %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:115 #, 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-ea89821/po/zh_CN.po000066400000000000000000001026371477177637400241220ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-02-14 22:51\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: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:24 msgid "GSConnect Team" msgstr "GSConnect 团队" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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:44 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:48 msgid "Share files, links and text" msgstr "发送文件、链接和文本" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "发送、接收消息" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "同步剪贴板内容" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "同步联系人" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "同步通知" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "控制媒体播放器" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "控制系统音量" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "执行预定义的命令" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "以及更多功能。" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GNOME Shell 中的 GSConnect" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "通知" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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 "自动挂载 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:367 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:390 msgid "Encryption Info" msgstr "加密信息" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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: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 "当 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:144 msgid "Discovery Disabled" msgstr "发现已禁用" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" msgstr "通过IP添加设备…" #: 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 连接" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "编辑" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "删除" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "禁用" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, 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:175 msgid "Click for help troubleshooting" msgstr "单击以获取疑难解答帮助" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "单击以获取详细信息" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "拨打号码" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "共享文件" #: src/service/daemon.js:337 msgid "List available devices" msgstr "可用设备列表" #: src/service/daemon.js:346 msgid "List all devices" msgstr "所有可用设备" #: src/service/daemon.js:355 msgid "Target Device" msgstr "目标设备" #: src/service/daemon.js:397 msgid "Message Body" msgstr "消息正文" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "发送通知" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "通知的应用名称" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "通知的信息正文" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "通知的图标" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "通知的ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "响铃" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "共享链接" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "共享文本" #: src/service/daemon.js:505 msgid "Show release version" msgstr "显示版本信息" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "位于 %s 的蓝牙设备" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "验证密钥: %s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "%s 请求配对" #: src/service/device.js:853 msgid "Reject" msgstr "拒绝" #: src/service/device.js:858 msgid "Accept" msgstr "接受" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "由于这个网络上的设备数量过多,发现已被禁用。" #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "无法找到 OpenSSL(OpenSSL not found)" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "端口已被占用" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "交换电池信息" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "充满电" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "已充电 %d%%" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s: 电池电量低" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "剩余 %d%%" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "剪贴板" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "共享剪贴板内容" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "推送剪贴板" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "获取剪贴板" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "访问配对设备的联系人" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "查找我的手机" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "让配对的设备响铃" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "鼠标" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "使配对的设备能够充当远程鼠标和键盘" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "远程输入" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "双向远程媒体播放控制" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "未知" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "与配对设备共享通知" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "取消通知" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "关闭通知" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "回复通知" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "激活通知" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "远程输入" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "将配对设备用作演示器" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "运行命令" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "在配对的设备上运行命令,或让该设备在此PC上运行预定义的命令" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "浏览配对设备的文件系统" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "挂载" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "卸载" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s 报告了一个错误" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "共享" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "在设备间共享文件和 URL" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "传输失败" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "%s 不允许上传文件" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "文件传输中" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "正在从 %2$s 接收 “%1$s”" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "传输成功" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "已从 %2$s 接收 “%1$s”" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "显示文件位置" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "打开文件" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "无法从 %2$s 接收 “%1$s”" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "%s 共享的文本" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "正在将 “%s” 发送到 %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "无法将 “%s” 发送到 %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "发送文件到 %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "完成后打开" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "发送链接到 %s" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "短信" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "发送和读取配对设备的短信,并接收新短信的通知" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "新短信(URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "回复短信" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "共享短信" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "系统音量" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "使配对的设备能够控制系统音量" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "未找到 PulseAudio" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "静音通话" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "来电" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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 分钟" #: 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 位联系人" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:115 #, 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 直接将链接发送到浏览器或短信。" #. 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-ea89821/po/zh_TW.po000066400000000000000000001031701477177637400241450ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-03 13:38-0500\n" "PO-Revision-Date: 2025-01-03 22:06\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:30 msgid "GSConnect" msgstr "GSConnect" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:13 msgid "KDE Connect implementation for GNOME" msgstr "GNOME 的 KDE Con​​nect 實現" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:24 msgid "GSConnect Team" msgstr "GSConnect 團隊" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:41 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 Con​​nect 的完整實現,特別是 GNOME Shell 與 Nautilus、Chrome、Firefox 三者的整合。 KDE Con​​nect 團隊擁有適用於 Linux、BSD、Android、Sailfish、iOS、macOS 和 Windows 的應用程式。" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:44 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:48 msgid "Share files, links and text" msgstr "分享檔案、連結和文字內容" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:49 msgid "Send and receive messages" msgstr "傳送和接收訊息" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:50 msgid "Sync clipboard content" msgstr "同步剪貼簿內容" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:51 msgid "Sync contacts" msgstr "同步聯絡人" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:52 msgid "Sync notifications" msgstr "同步通知" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:53 msgid "Control media players" msgstr "控制媒體播放器" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:54 msgid "Control system volume" msgstr "控制系統音量" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:55 msgid "Execute predefined commands" msgstr "執行預定義命令" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:56 msgid "And more…" msgstr "還有更多……" #: data/metainfo/org.gnome.Shell.Extensions.GSConnect.metainfo.xml.in:141 msgid "GSConnect in GNOME Shell" msgstr "GNOME Shell 中的 GSConnect" #: data/ui/connect-dialog.ui:20 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:161 #: src/service/plugins/share.js:297 src/service/plugins/share.js:428 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:274 #: src/service/daemon.js:388 src/service/plugins/sms.js:62 #: 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:429 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:54 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:30 #: 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:331 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:14 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:26 src/service/plugins/runcommand.js:34 #: src/service/plugins/runcommand.js:194 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:17 msgid "Notifications" msgstr "通知" #: data/ui/preferences-device-panel.ui:1278 src/service/plugins/contacts.js:27 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:15 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 "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:367 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:390 msgid "Encryption Info" msgstr "加密資訊" #. TRANSLATORS: Unpair the device and notify it #: data/ui/preferences-device-panel.ui:2606 src/service/daemon.js:376 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:197 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: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 "當 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:144 msgid "Discovery Disabled" msgstr "探索已被停用" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/ui/preferences-window.ui:821 msgid "Add device by IP…" 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 台裝置已連結" #: src/preferences/device.js:669 src/preferences/device.js:675 msgid "Edit" msgstr "編輯" #: src/preferences/device.js:684 src/preferences/device.js:690 msgid "Remove" msgstr "移除" #: src/preferences/device.js:944 src/preferences/device.js:972 msgid "Disabled" msgstr "停用" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/preferences/keybindings.js:66 #, 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:132 #, javascript-format msgid "%s is already being used" msgstr "「%s」已經被使用" #: src/preferences/service.js:361 msgid "A complete KDE Connect implementation for GNOME" msgstr "一個為 GNOME 桌面環境設計的 KDE Connect 完整實作" #. 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:175 msgid "Click for help troubleshooting" msgstr "點擊以幫助除錯" #: src/service/daemon.js:186 msgid "Click for more information" msgstr "按一下以瞭解詳情" #: src/service/daemon.js:280 msgid "Dial Number" msgstr "撥打電話" #: src/service/daemon.js:286 src/service/daemon.js:475 #: src/service/plugins/share.js:31 msgid "Share File" msgstr "分享檔案" #: src/service/daemon.js:337 msgid "List available devices" msgstr "列出可用裝置" #: src/service/daemon.js:346 msgid "List all devices" msgstr "列出所有裝置" #: src/service/daemon.js:355 msgid "Target Device" msgstr "目標裝置" #: src/service/daemon.js:397 msgid "Message Body" msgstr "訊息內文" #: src/service/daemon.js:409 src/service/plugins/notification.js:56 msgid "Send Notification" msgstr "傳送通知" #: src/service/daemon.js:418 msgid "Notification App Name" msgstr "通知程式名稱" #: src/service/daemon.js:427 msgid "Notification Body" msgstr "通知內容" #: src/service/daemon.js:436 msgid "Notification Icon" msgstr "通知圖示" #: src/service/daemon.js:445 msgid "Notification ID" msgstr "通知 ID" #: src/service/daemon.js:454 src/service/plugins/ping.js:13 #: src/service/plugins/ping.js:20 src/service/plugins/ping.js:47 msgid "Ping" msgstr "Ping" #: src/service/daemon.js:463 src/service/plugins/battery.js:246 #: src/service/plugins/battery.js:275 src/service/plugins/battery.js:304 #: src/service/plugins/findmyphone.js:22 msgid "Ring" msgstr "響鈴" #: src/service/daemon.js:484 src/service/plugins/share.js:47 #: src/service/ui/messaging.js:1251 src/service/ui/messaging.js:1259 msgid "Share Link" msgstr "分享連結" #: src/service/daemon.js:493 src/service/plugins/share.js:39 msgid "Share Text" msgstr "分享文字" #: src/service/daemon.js:505 msgid "Show release version" msgstr "顯示發布版本" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:171 #, javascript-format msgid "Bluetooth device at %s" msgstr "在「%s」的藍牙裝置" #. TRANSLATORS: Label for TLS connection verification key #. #. Example: #. #. Verification key: 0123456789abcdef000000000000000000000000 #: src/service/device.js:212 #, javascript-format msgid "Verification key: %s" msgstr "驗證金鑰:%s" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:846 #, javascript-format msgid "Pair Request from %s" msgstr "「%s」的配對請求" #: src/service/device.js:853 msgid "Reject" msgstr "拒絕" #: src/service/device.js:858 msgid "Accept" msgstr "接受" #: src/service/manager.js:145 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "因為此網域內的裝置數量,探索已停用" #: src/service/backends/lan.js:177 msgid "OpenSSL not found" msgstr "未找到 OpenSSL" #: src/service/backends/lan.js:470 msgid "Port already in use" msgstr "連接埠已在使用中" #: src/service/plugins/battery.js:15 msgid "Exchange battery information" msgstr "交換電池資訊" #. TRANSLATORS: eg. Google Pixel: Battery is full #: src/service/plugins/battery.js:255 #, 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:257 src/shell/device.js:119 msgid "Fully Charged" msgstr "充電完成" #. TRANSLATORS: eg. Google Pixel: Battery has reached custom charge level #: src/service/plugins/battery.js:284 #, 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:286 #, javascript-format msgid "%d%% Charged" msgstr "%d%% 已充電" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:313 #, javascript-format msgid "%s: Battery is low" msgstr "%s:電量過低" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:315 #, javascript-format msgid "%d%% remaining" msgstr "剩下 %d%%" #: src/service/plugins/clipboard.js:12 msgid "Clipboard" msgstr "剪貼簿" #: src/service/plugins/clipboard.js:13 msgid "Share the clipboard content" msgstr "分享剪貼簿內容" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Push" msgstr "推送剪貼簿" #: src/service/plugins/clipboard.js:33 msgid "Clipboard Pull" msgstr "接收剪貼簿" #: src/service/plugins/contacts.js:28 msgid "Access contacts of the paired device" msgstr "取用配對裝置的聯絡人" #: src/service/plugins/findmyphone.js:15 msgid "Find My Phone" msgstr "找尋我的手機" #: src/service/plugins/findmyphone.js:16 msgid "Ring your paired device" msgstr "讓您的配對裝置鈴聲響起" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "觸控板" #: src/service/plugins/mousepad.js:15 msgid "Enables the paired device to act as a remote mouse and keyboard" msgstr "讓配對裝置充當遠端滑鼠和鍵盤" #: src/service/plugins/mousepad.js:29 src/service/ui/mousepad.js:107 msgid "Remote Input" msgstr "遠端輸入" #: src/service/plugins/mpris.js:17 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/mpris.js:18 msgid "Bidirectional remote media playback control" msgstr "雙向遠端媒體播放控制" #: src/service/plugins/mpris.js:329 msgid "Unknown" msgstr "未知" #: src/service/plugins/notification.js:18 msgid "Share notifications with the paired device" msgstr "與配對裝置共享通知" #: src/service/plugins/notification.js:32 msgid "Cancel Notification" msgstr "取消通知" #: src/service/plugins/notification.js:40 msgid "Close Notification" msgstr "關閉通知" #: src/service/plugins/notification.js:48 msgid "Reply Notification" msgstr "回覆通知" #: src/service/plugins/notification.js:64 msgid "Activate Notification" msgstr "啟用通知" #: src/service/plugins/ping.js:14 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:54 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/presenter.js:12 msgid "Presentation" msgstr "簡報" #: src/service/plugins/presenter.js:13 msgid "Use the paired device as a presenter" msgstr "使用配對的裝置作為簡報器" #: src/service/plugins/runcommand.js:13 msgid "Run Commands" msgstr "執行命令" #: src/service/plugins/runcommand.js:15 msgid "Run commands on your paired device or let the device run predefined commands on this PC" msgstr "在配對裝置上執行命令或讓裝置在此電腦上執行預定義命令" #: src/service/plugins/sftp.js:14 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:16 msgid "Browse the paired device filesystem" msgstr "瀏覽已配對裝置的檔案系統" #: src/service/plugins/sftp.js:21 msgid "Mount" msgstr "掛載" #: src/service/plugins/sftp.js:29 msgid "Unmount" msgstr "卸載" #: src/service/plugins/sftp.js:190 #, javascript-format msgid "%s reported an error" msgstr "%s 回報了一個錯誤" #: src/service/plugins/share.js:16 src/service/plugins/share.js:23 msgid "Share" msgstr "分享" #: src/service/plugins/share.js:18 msgid "Share files and URLs between devices" msgstr "在裝置之間分享檔案和網址" #: src/service/plugins/share.js:130 src/service/plugins/share.js:209 #: src/service/plugins/share.js:320 msgid "Transfer Failed" msgstr "傳輸失敗" #. TRANSLATORS: eg. Google Pixel is not allowed to upload files #: src/service/plugins/share.js:132 #, javascript-format msgid "%s is not allowed to upload files" msgstr "不允許「%s」上傳檔案" #: src/service/plugins/share.js:154 src/service/plugins/share.js:290 msgid "Transferring File" msgstr "傳送檔案中" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:156 #, javascript-format msgid "Receiving “%s” from %s" msgstr "正在接收「%s」,來自於「%s」" #: src/service/plugins/share.js:175 src/service/plugins/share.js:310 msgid "Transfer Successful" msgstr "傳輸成功" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:177 #, javascript-format msgid "Received “%s” from %s" msgstr "已接收「%s」,來自於「%s」" #: src/service/plugins/share.js:187 msgid "Show File Location" msgstr "顯示檔案位置" #: src/service/plugins/share.js:192 msgid "Open File" msgstr "開啟檔案" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:211 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "接收「%s」失敗,來自於「%s」" #: src/service/plugins/share.js:242 #, javascript-format msgid "Text Shared By %s" msgstr "「%s」分享的文字" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:292 #, javascript-format msgid "Sending “%s” to %s" msgstr "正在將「%s」傳送到「%s」" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:312 #, 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:322 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "傳送「%s」到「%s」失敗" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:380 #, javascript-format msgid "Send files to %s" msgstr "將檔案傳送到「%s」" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:384 msgid "Open when done" msgstr "當完成時開啟" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:423 #, javascript-format msgid "Send a link to %s" msgstr "傳送一個連結到「%s」" #: src/service/plugins/sms.js:16 msgid "SMS" msgstr "簡訊" #: src/service/plugins/sms.js:17 msgid "Send and read SMS of the paired device and be notified of new SMS" msgstr "傳送和讀取配對裝置的簡訊並收到新簡訊通知" #: src/service/plugins/sms.js:38 msgid "New SMS (URI)" msgstr "新增簡訊 (URI)" #: src/service/plugins/sms.js:46 msgid "Reply SMS" msgstr "回覆簡訊" #: src/service/plugins/sms.js:70 msgid "Share SMS" msgstr "分享簡訊" #: src/service/plugins/systemvolume.js:13 msgid "System Volume" msgstr "系統音量" #: src/service/plugins/systemvolume.js:14 msgid "Enable the paired device to control the system volume" msgstr "啟用配對裝置控制系統音量" #: src/service/plugins/systemvolume.js:58 msgid "PulseAudio not found" msgstr "未找到 PulseAudio" #: src/service/plugins/telephony.js:16 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:28 msgid "Mute Call" msgstr "通話靜音" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #. Contact Name #: src/service/plugins/telephony.js:155 src/service/plugins/telephony.js:174 #: 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:193 msgid "Incoming call" msgstr "來電" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:208 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 分鐘" #: 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 位其他聯絡人" #. TRANSLATORS: Displayed when the remote keyboard is not ready to accept input #: src/service/ui/mousepad.js:115 #, 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 分享連結至裝置的 Web 瀏覽器或是經由簡訊傳送。" #. 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-ea89821/pyproject.toml000066400000000000000000000003751477177637400250530ustar00rootroot00000000000000# SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect # # SPDX-License-Identifier: GPL-2.0-or-later [tool.black] line-length = 80 target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] include = 'nautilus-extension/.*\.py$' GSConnect-gnome-shell-extension-gsconnect-ea89821/src/000077500000000000000000000000001477177637400227215ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/src/extension.js000066400000000000000000000322361477177637400253010ustar00rootroot00000000000000// 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 Remote from './utils/remote.js'; import * as 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.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. Setup.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. Setup.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-ea89821/src/gsconnect-preferences000077500000000000000000000056131477177637400271360ustar00rootroot00000000000000#!/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-ea89821/src/preferences/000077500000000000000000000000001477177637400252225ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/src/preferences/device.js000066400000000000000000001040121477177637400270150ustar00rootroot00000000000000// 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 * @returns {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 { resolve(false); } } ); }); this.battery_system_label.visible = hasBattery; this.battery_system.visible = hasBattery; } catch { 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 { 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 { 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-ea89821/src/preferences/init.js000066400000000000000000000005241477177637400265240ustar00rootroot00000000000000// 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-ea89821/src/preferences/keybindings.js000066400000000000000000000217411477177637400300730ustar00rootroot00000000000000// 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 * @returns {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 * @returns {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-ea89821/src/preferences/service.js000066400000000000000000000467371477177637400272410ustar00rootroot00000000000000// 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(); } _validateName(name) { // None of the forbidden characters and at least one non-whitespace if (name.trim() && /^[^"',;:.!?()[\]<>]{1,32}$/.test(name)) return true; const dialog = new Gtk.MessageDialog({ text: _('Invalid Device Name'), // TRANSLATOR: %s is a list of forbidden characters secondary_text: _('Device name must not contain any of %s ' + 'and have a length of 1-32 characters') .format('^"\',;:.!?()[]<>'), secondary_use_markup: true, buttons: Gtk.ButtonsType.OK, modal: true, transient_for: this, }); dialog.connect('response', (dialog) => dialog.destroy()); dialog.show_all(); return false; } /* * "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._validateName(this.rename_entry.text)) { 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-ea89821/src/prefs.js000066400000000000000000000016111477177637400243750ustar00rootroot00000000000000// 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 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.setup(this.path); Setup.ensurePermissions(); Setup.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-ea89821/src/service/000077500000000000000000000000001477177637400243615ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/src/service/backends/000077500000000000000000000000001477177637400261335ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/src/service/backends/lan.js000066400000000000000000000700531477177637400272500ustar00rootroot00000000000000// 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'; import Device from '../device.js'; // Retain compatibility with GLib < 2.80, which lacks GioUnix let GioUnix; try { GioUnix = (await import('gi://GioUnix')).default; } catch { 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 { _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 id() { return this.certificate.common_name; } 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() { 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, null); } _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 { 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 (!this.identity.body.deviceId) throw new Error('missing deviceId'); // Silently ignore our own broadcasts if (packet.body.deviceId === this.identity.body.deviceId) return; // Reject invalid device IDs if (!Device.validateId(packet.body.deviceId)) throw new Error(`invalid deviceId "${packet.body.deviceId}"`); if (!packet.body.deviceName) throw new Error('missing deviceName'); // Reject invalid device names if (!Device.validateName(packet.body.deviceName)) throw new Error(`invalid deviceName "${packet.body.deviceName}"`); 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 * @returns {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 * @returns {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 * @returns {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'); // Reject invalid device IDs if (!Device.validateId(this.identity.body.deviceId)) throw new Error(`invalid deviceId "${this.identity.body.deviceId}"`); if (!this.identity.body.deviceName) throw new Error('missing deviceName'); // Reject invalid device names if (!Device.validateName(this.identity.body.deviceName)) throw new Error(`invalid deviceName "${this.identity.body.deviceName}"`); this._connection = await this._encryptClient(connection); // Starting with protocol version 8, the devices are expected to // exchange identity packets again after TLS negotiation if (this.identity.body.protocolVersion >= 8) { await this.sendPacket(this.backend.identity); this.identity = await this.readPacket(); } } 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); // Starting with protocol version 8, the devices are expected to // exchange identity packets again after TLS negotiation if (this.identity.body.protocolVersion >= 8) { await this.sendPacket(this.backend.identity); this.identity = await this.readPacket(); } } 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-ea89821/src/service/components/000077500000000000000000000000001477177637400265465ustar00rootroot00000000000000GSConnect-gnome-shell-extension-gsconnect-ea89821/src/service/components/atspi.js000066400000000000000000000222021477177637400302220ustar00rootroot00000000000000// 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 { 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 { // Silence errors } } } GSConnect-gnome-shell-extension-gsconnect-ea89821/src/service/components/clipboard.js000066400000000000000000000146121477177637400310470ustar00rootroot00000000000000// 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( null, 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-ea89821/src/service/components/contacts.js000066400000000000000000000431071477177637400307270ustar00rootroot00000000000000// 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 { 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 EDataServer.SourceRegistry.new(null); 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 * @returns {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 * @returns {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 * @returns {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-ea89821/src/service/components/index.js000066400000000000000000000043131477177637400302140ustar00rootroot00000000000000// 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 * @returns {*} 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 * @returns {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-ea89821/src/service/components/input.js000066400000000000000000000315321477177637400302470ustar00rootroot00000000000000// 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 = 300; 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 { 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() { // Update the timestamp of the last event this._sessionExpiry = Math.floor((Date.now() / 1000) + SESSION_TIMEOUT); // Session is active if (this._session !== null) return this._session; // Mutter's RemoteDesktop is not available, fall back to Atspi if (this.connection === null) { debug('Falling back to Atspi'); this._session = new AtspiController(); return this._session; } try { // Mutter is available and there isn't another session starting 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; } return this._session; } catch (e) { logError(e); if (this._session !== null) { this._session.destroy(); this._session = null; } this._sessionStarting = false; throw e; } } /* * Pointer Events */ movePointer(dx, dy) { if (dx === 0 && dy === 0) return; this._ensureAdapter() .then(session => session?.movePointer(dx, dy)) .catch(e => debug(e)); } pressPointer(button) { this._ensureAdapter() .then(session => session?.pressPointer(button)) .catch(e => debug(e)); } releasePointer(button) { this._ensureAdapter() .then(session => session?.releasePointer(button)) .catch(e => debug(e)); } clickPointer(button) { this._ensureAdapter() .then(session => session?.clickPointer(button)) .catch(e => debug(e)); } doubleclickPointer(button) { this._ensureAdapter() .then(session => session?.doubleclickPointer(button)) .catch(e => debug(e)); } scrollPointer(dx, dy) { if (dx === 0 && dy === 0) return; this._ensureAdapter() .then(session => session?.scrollPointer(dx, dy)) .catch(e => debug(e)); } /* * Keyboard Events */ pressKeysym(keysym) { this._ensureAdapter() .then(session => session?.pressKeysym(keysym)) .catch(e => debug(e)); } releaseKeysym(keysym) { this._ensureAdapter() .then(session => session?.releaseKeysym(keysym)) .catch(e => debug(e)); } pressreleaseKeysym(keysym) { this._ensureAdapter() .then(session => session?.pressreleaseKeysym(keysym)) .catch(e => debug(e)); } /* * High-level keyboard input */ pressKeys(input, modifiers) { this._ensureAdapter() .then(session => { if (typeof input === 'string') { for (let i = 0; i < input.length; i++) session?.pressKey(input[i], modifiers); } else { 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-ea89821/src/service/components/mpris.js000066400000000000000000000621541477177637400302460ustar00rootroot00000000000000// 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 { /** * @constructs GSConnectMPRISPlayer * @param {string} name - The name of the 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 { 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 { 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 * @returns {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 * @returns {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. * * @returns {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-ea89821/src/service/components/notification.js000066400000000000000000000316131477177637400315760ustar00rootroot00000000000000// 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) * @returns {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() * @returns {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 { // 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 * * @returns {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-ea89821/src/service/components/pulseaudio.js000066400000000000000000000152021477177637400312560ustar00rootroot00000000000000// 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 {} /** * 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 { 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-ea89821/src/service/components/session.js000066400000000000000000000042131477177637400305670ustar00rootroot00000000000000// 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-ea89821/src/service/components/sound.js000066400000000000000000000113401477177637400302330ustar00rootroot00000000000000// 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 {} 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-ea89821/src/service/components/upower.js000066400000000000000000000123201477177637400304230ustar00rootroot00000000000000// 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-ea89821/src/service/components/ydotool.js000066400000000000000000000072311477177637400306000ustar00rootroot00000000000000// 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-ea89821/src/service/core.js000066400000000000000000000430711477177637400256540ustar00rootroot00000000000000// 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 Device from './device.js'; import plugins from './plugins/index.js'; /** * Get the local device type. * * @returns {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 ([30, 32].includes(type)) return 'tablet'; if ([8, 9, 10, 14, 31].includes(type)) return 'laptop'; return 'desktop'; } catch { 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 * @returns {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. * * @returns {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. * * @returns {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 * @returns {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 {Packet} packet - The packet to send * @param {Gio.Cancellable} [cancellable] - A cancellable * @returns {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 {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 {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 {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().slice(0, 32); 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 = Device.generateId(); 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: 8, 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 {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 {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 {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 {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-ea89821/src/service/daemon.js000077500000000000000000000537071477177637400262010ustar00rootroot00000000000000#!/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 Device from './device.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(); } _migrateConfiguration() { if (!Device.validateName(this.settings.get_string('name'))) this.settings.set('name', GLib.get_host_name().slice(0, 32)); const [certPath, keyPath] = [ GLib.build_filenamev([Config.CONFIGDIR, 'certificate.pem']), GLib.build_filenamev([Config.CONFIGDIR, 'private.pem']), ]; const certificate = Gio.TlsCertificate.new_for_paths(certPath, keyPath, null); if (Device.validateId(certificate.common_name)) return; // Remove the old certificate, serving as the single source of truth // for the device ID try { Gio.File.new_for_path(certPath).delete(null); Gio.File.new_for_path(keyPath).delete(null); } catch { // Silence errors } // For each device, remove it entirely if it violates the device ID // constraints, otherwise mark it unpaired to preserve the settings. const deviceList = this.settings.get_strv('devices').filter(id => { const settingsPath = `/org/gnome/shell/extensions/gsconnect/device/${id}/`; if (!Device.validateId(id)) { GLib.spawn_command_line_async(`dconf reset -f ${settingsPath}`); Gio.File.rm_rf(GLib.build_filenamev([Config.CACHEDIR, id])); debug(`Invalid device ID ${id} removed.`); return false; } const settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup( 'org.gnome.Shell.Extensions.GSConnect.Device', true), path: settingsPath, }); settings.set_boolean('paired', false); return true; }); this.settings.set_strv('devices', deviceList); // Notify the user const notification = Gio.Notification.new(_('Settings Migrated')); notification.set_body(_('GSConnect has updated to support changes to the KDE Connect protocol. Some devices may need to be repaired.')); notification.set_icon(new Gio.ThemedIcon({name: 'dialog-warning'})); notification.set_priority(Gio.NotificationPriority.HIGH); this.send_notification('settings-migrated', notification); // Finally, reset the service ID to trigger re-generation. this.settings.reset('id'); } 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(); // TODO: remove after a reasonable period of time this._migrateConfiguration(); 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-ea89821/src/service/device.js�����������������������������0000664�0000000�0000000�00000104021�14771776374�0026154�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'; const ALLOWED_TIMESTAMP_TIME_DIFFERENCE_SECONDS = 1800; // 30 min /** * 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; this._pairingTimestamp = 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(); } static generateId() { return GLib.uuid_string_random().replaceAll('-', ''); } static validateId(id) { return /^[a-zA-Z0-9_-]{32,38}$/.test(id); } static validateName(name) { // None of the forbidden characters and at least one non-whitespace return name.trim() && /^[^"',;:.!?()[\]<>]{1,32}$/.test(name); } 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() { if (!this.channel) return ''; // 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('???'); } // FIXME: another ugly reach-around const localCert = this.service.manager.backends.get('lan')?.certificate; const remoteCert = this.channel?.peer_certificate; if (!localCert || !remoteCert) return ''; const checksum = new GLib.Checksum(GLib.ChecksumType.SHA256); let [a, b] = [localCert.pubkey_der(), remoteCert.pubkey_der()]; if (a.compare(b) < 0) [a, b] = [b, a]; // swap checksum.update(a.toArray()); checksum.update(b.toArray()); if (this.channel?.identity.body.protocolVersion >= 8) checksum.update(String(this._pairingTimestamp)); const verificationKey = checksum.get_string() .substring(0, 8) .toUpperCase(); // 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 * @returns {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 * @returns {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) * @returns {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 { 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 * @returns {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 * @returns {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 * @returns {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. * * @returns {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 * @returns {void} */ rejectTransfer(packet) { if (packet?.hasPayload()) 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._pairingTimestamp = Math.floor(Date.now() / 1000); this.sendPacket({ type: 'kdeconnect.pair', body: { pair: true, timestamp: this._pairingTimestamp, }, }); this._triggerPlugins(); // The device is requesting pairing } else { this._notifyPairRequest(packet.body?.timestamp); } // 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 * * @param {number} [timestamp] - Timestamp for the pair request */ _notifyPairRequest(timestamp = 0) { // Reset any active request this._resetPairRequest(); this._pairingTimestamp = timestamp; 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; } this._pairingTimestamp = 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) { if (this.identity?.body.protocolVersion >= 8) { const currentTimestamp = Math.floor(Date.now() / 1000); const diffTimestamp = Number.abs(this._pairingTimestamp - currentTimestamp); if (diffTimestamp > ALLOWED_TIMESTAMP_TIME_DIFFERENCE_SECONDS) { this._setPaired(false); this.showNotification({ id: 'pair-request', // TRANSLATORS: eg. Failed to pair with Google Pixel title: _('Failed to pair with %s').format(this.name), body: _('Device clocks are out of sync'), icon: new Gio.ThemedIcon({name: 'dialog-warning-symbolic'}), priority: Gio.NotificationPriority.URGENT, }); return; } } 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._pairingTimestamp = Math.floor(Date.now() / 1000); this.sendPacket({ type: 'kdeconnect.pair', body: { pair: true, timestamp: this._pairingTimestamp, }, }); 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-ea89821/src/service/init.js�������������������������������0000664�0000000�0000000�00000034010�14771776374�0025660�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, }); }; globalThis._debugFunc = _debugFunc; const settings = new Gio.Settings({ settings_schema: Config.GSCHEMA.lookup(Config.APP_ID, true), }); if (settings.get_boolean('debug')) { globalThis.debug = globalThis._debugFunc; } else { // Swap the function out for a no-op anonymous function for speed 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 * * @returns {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 * @returns {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 { // Silence errors } file.delete(null); } catch { // 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. * @returns {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. * @returns {*} 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 { 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 * @returns {Gio.TlsCertificate} A TLS certificate */ Gio.TlsCertificate.new_for_paths = function (certPath, keyPath, commonName = null) { 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; } // 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().replaceAll('-', ''); const proc = new Gio.Subprocess({ argv: [ Config.OPENSSL_PATH, 'req', '-newkey', 'ec', '-pkeyopt', 'ec_paramgen_curve:prime256v1', '-keyout', keyPath, '-new', '-x509', '-nodes', '-days', '3650', '-subj', `/O=andyholmes.github.io/OU=GSConnect/CN=${commonName}`, '-out', certPath, ], 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. * * @returns {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-ea89821/src/service/manager.js����������������������������0000664�0000000�0000000�00000035421�14771776374�0026336�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 Core from './core.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 ), 'debug': GObject.ParamSpec.boolean( 'debug', 'Debug', 'Whether debug logging is enabled in GSConnect', GObject.ParamFlags.READWRITE, false ), 'certificate': GObject.ParamSpec.object( 'certificate', 'Certificate', 'The local TLS certificate', GObject.ParamFlags.READABLE, Gio.TlsCertificate ), '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.READABLE, 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 certificate() { if (this._certificate === undefined) { this._certificate = Gio.TlsCertificate.new_for_paths( GLib.build_filenamev([Config.CONFIGDIR, 'certificate.pem']), GLib.build_filenamev([Config.CONFIGDIR, 'private.pem']), null); } return this._certificate; } get debug() { if (this._debug === undefined) this._debug = this.settings.get_boolean('debug'); return this._debug; } set debug(value) { if (this._debug === value) return; this._debug = value; this._onDebugChanged(this._debug); } 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() { return this.certificate.common_name; } 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() { if (this.settings.get_string('name').length === 0) this.settings.set_string('name', GLib.get_host_name()); // Bound Properties this.settings.bind('debug', this, 'debug', 0); this.settings.bind('discoverable', this, 'discoverable', 0); this.settings.bind('name', this, 'name', 0); } _onDebugChanged(debug = false) { // If debugging is disabled, install a no-op for speed if (debug && globalThis._debugFunc !== undefined) globalThis.debug = globalThis._debugFunc; else globalThis.debug = () => {}; } /* * 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 * @returns {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/device/${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. * * @returns {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-ea89821/src/service/nativeMessagingHost.js����������������0000775�0000000�0000000�00000014632�14771776374�0030712�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 { 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 { this.quit(); } } send(message) { try { const data = JSON.stringify(message); this._stdout.put_int32(data.length, null); this._stdout.put_string(data, null); } catch { 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 { 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-ea89821/src/service/plugin.js�����������������������������0000664�0000000�0000000�00000016113�14771776374�0026217�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 Core from './core.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-ea89821/src/service/plugins/������������������������������0000775�0000000�0000000�00000000000�14771776374�0026042�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/src/service/plugins/battery.js��������������������0000664�0000000�0000000�00000030257�14771776374�0030061�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 * as Core from '../core.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-ea89821/src/service/plugins/clipboard.js������������������0000664�0000000�0000000�00000012063�14771776374�0030341�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-ea89821/src/service/plugins/connectivity_report.js��������0000664�0000000�0000000�00000011770�14771776374�0032517�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 Core from '../core.js'; 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-ea89821/src/service/plugins/contacts.js�������������������0000664�0000000�0000000�00000033322�14771776374�0030221�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'; import * as Core from '../core.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 { 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 * @returns {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 * @returns {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 { 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 * @returns {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-ea89821/src/service/plugins/findmyphone.js����������������0000664�0000000�0000000�00000014513�14771776374�0030724�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-ea89821/src/service/plugins/index.js����������������������0000664�0000000�0000000�00000002070�14771776374�0027506�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-ea89821/src/service/plugins/mousepad.js�������������������0000664�0000000�0000000�00000023667�14771776374�0030233�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 */ _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-ea89821/src/service/plugins/mpris.js����������������������0000664�0000000�0000000�00000067720�14771776374�0027546�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 Core from '../core.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._queueTimers = new Map(); 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, timer] of this._queueTimers) { if (timer) GLib.source_remove(timer); this._queueTimers.delete(identity); } 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` * @returns {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); } } if (packet.body.hasOwnProperty('requestNowPlaying') || packet.body.hasOwnProperty('requestVolume')) { const response = this._getUpdate(player.Identity, packet); this._sendUpdate(player, response); } } catch (e) { debug(e, this.device.name); } finally { this._updating.delete(player); } } // Respond to information request (or push updated information) _getUpdate(identity, packet) { const player = this._mpris?.getPlayer(identity); if (!player) return; const response = { type: 'kdeconnect.mpris', body: { player: player.Identity, }, }; try { if (packet.body.hasOwnProperty('requestNowPlaying')) { 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')) response.body.volume = Math.floor(player.Volume * 100); return response; } catch (e) { debug(e, this.device.name); } } _sendUpdate(player, packet = null) { if (!player || (!packet && this._updating.has(player))) return GLib.SOURCE_REMOVE; debug(`Sending update for ${player.Identity}`); this._updating.add(player); if (this._queueTimers.has(player.Identity)) { const timer_id = this._queueTimers.get(player.Identity); if (timer_id) { debug(`Stopping timer id ${timer_id}`); GLib.source_remove(timer_id); } this._queueTimers.delete(player.Identity); } if (!packet) { packet = this._getUpdate(player.Identity, { body: { requestNowPlaying: true, requestVolume: true, }, }, false); } this.device.sendPacket(packet); this._updating.delete(player); return GLib.SOURCE_REMOVE; } _onPlayerChanged(mpris, player) { if (!this.settings.get_boolean('share-players')) return; // Set a timer to send the updated state after a short delay. // Allows further state changes to be bundled into a single packet. if (this._queueTimers.has(player.Identity)) return; this._queueTimers.set(player.Identity, 0); const timer_id = GLib.timeout_add( GLib.PRIORITY_DEFAULT, 250, // ms (0.25 seconds) this._sendUpdate.bind(this, player) ); this._queueTimers.set(player.Identity, timer_id); debug(`Set update timer id ${timer_id} for ${player.Identity}`); } _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() { for (const [identity, timer] of this._queueTimers) { this._queueTimers.delete(identity); GLib.source_remove(timer); } 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-ea89821/src/service/plugins/notification.js���������������0000664�0000000�0000000�00000054373�14771776374�0031102�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 * as Core from '../core.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` * @returns {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 * @returns {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-ea89821/src/service/plugins/ping.js�����������������������0000664�0000000�0000000�00000003464�14771776374�0027344�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-ea89821/src/service/plugins/presenter.js������������������0000664�0000000�0000000�00000003441�14771776374�0030411�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-ea89821/src/service/plugins/runcommand.js�����������������0000664�0000000�0000000�00000016476�14771776374�0030561�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 { 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-ea89821/src/service/plugins/sftp.js�����������������������0000664�0000000�0000000�00000035210�14771776374�0027355�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 Core from '../core.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. * * @returns {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-ea89821/src/service/plugins/share.js����������������������0000664�0000000�0000000�00000037715�14771776374�0027517�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'; const gtk_recent_manager = Gtk.RecentManager.get_default(); gtk_recent_manager.add_item(file.get_uri()); 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 { chooser.preview_widget.visible = false; chooser.preview_widget_active = false; } } _onUriButtonToggled(button) { const header = this.get_header_bar(); // Show and focus the URL entry if (button.active) { this.extra_widget.sensitive = false; header.set_custom_title(this._uriEntry); this._uriEntry.grab_focus(); 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-ea89821/src/service/plugins/sms.js������������������������0000664�0000000�0000000�00000036306�14771776374�0027212�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 * @returns {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-ea89821/src/service/plugins/systemvolume.js���������������0000664�0000000�0000000�00000014050�14771776374�0031154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// 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 * as Components from '../components/index.js'; import Config from '../../config.js'; import * as Core from '../core.js'; import Plugin from '../plugin.js'; 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 {} 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; } } /** * 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 * @returns {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-ea89821/src/service/plugins/telephony.js������������������0000664�0000000�0000000�00000016135�14771776374�0030415�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 * as Core from '../core.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 * @returns {GdkPixbuf.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-ea89821/src/service/ui/�����������������������������������0000775�0000000�0000000�00000000000�14771776374�0024776�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/src/service/ui/contacts.js������������������������0000664�0000000�0000000�00000043666�14771776374�0027171�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 * @returns {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 * @returns {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 * @returns {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 * @returns {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); } /** * Retrieve the GdkPixbuf for a named icon * * @param {string} name - The icon name to load * @param {number} size - The pixel size requested * @param {number} scale - The scale multiplier * @param {string} bgColor - The background color the icon will be used against * @returns {GdkPixbuf.pixbuf|null} The icon image */ 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 * @returns {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 * @returns {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. * * @returns {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-ea89821/src/service/ui/legacyMessaging.js�����������������0000664�0000000�0000000�00000014166�14771776374�0030446�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-ea89821/src/service/ui/messaging.js�����������������������0000664�0000000�0000000�00000113624�14771776374�0027320�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) * @returns {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) * @returns {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) * @returns {string} A localized timestamp */ function getDetailedTime(time) { return _cFormat.format(time); } /** * Make the avatar for an incoming message visible or invisible * * @param {ConversationMessage} row - The message row to modify * @param {boolean} visible - Whether the avatar should be visible */ 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 * @returns {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 * @returns {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 * @returns {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-ea89821/src/service/ui/mousepad.js������������������������0000664�0000000�0000000�00000032070�14771776374�0027153�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-ea89821/src/service/ui/notification.js��������������������0000664�0000000�0000000�00000010754�14771776374�0030031�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-ea89821/src/service/ui/service.js�������������������������0000664�0000000�0000000�00000015436�14771776374�0027005�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-ea89821/src/service/utils/��������������������������������0000775�0000000�0000000�00000000000�14771776374�0025521�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/src/service/utils/dbus.js�������������������������0000664�0000000�0000000�00000020042�14771776374�0027012�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 */ /** * Convert a label from kebab-case to CamelCase. * Will also remove snake_case separators (without capitalizing) * * @param {string} string - The label to reformat * @returns {string} The CamelCased label */ function toDBusCase(string) { return string.replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, offset) => { return ltr.toUpperCase(); }).replace(/[\s_-]+/g, ''); } /** * Convert a label from CamelCase to snake_case. * * @param {string} string - The label to reformat * @returns {string} - The snake_cased label */ 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 {Gio.DBusInterface} 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 * @returns {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-ea89821/src/service/utils/ui.js���������������������������0000664�0000000�0000000�00000002620�14771776374�0026474�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-ea89821/src/service/utils/uri.js��������������������������0000664�0000000�0000000�00000013040�14771776374�0026654�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 * @returns {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) * @returns {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-ea89821/src/shell/����������������������������������������0000775�0000000�0000000�00000000000�14771776374�0024030�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/src/shell/clipboard.js����������������������������0000664�0000000�0000000�00000025061�14771776374�0026331�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 { 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 * * @returns {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 * * @returns {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 * @returns {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 * @returns {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 * @returns {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-ea89821/src/shell/device.js�������������������������������0000664�0000000�0000000�00000026001�14771776374�0025624�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-ea89821/src/shell/gmenu.js��������������������������������0000664�0000000�0000000�00000046364�14771776374�0025516�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 {HAS_ST_ORIENTATION, 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 * @returns {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 = HAS_ST_ORIENTATION ? new St.BoxLayout({ clip_to_allocation: true, orientation: Clutter.Orientation.HORIZONTAL, // GNOME 48 visible: false, x_expand: true, }) : new St.BoxLayout({ clip_to_allocation: true, vertical: false, // GNOME 46/47 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 = HAS_ST_ORIENTATION ? new St.BoxLayout({ orientation: Clutter.Orientation.VERTICAL, // GNOME 48 x_expand: true, }) : new St.BoxLayout({ vertical: true, // GNOME 46/47 x_expand: true, }); this.actor._delegate = this; // Button Box this.box._delegate = this; this.box.style_class = 'gsconnect-icon-box'; if (HAS_ST_ORIENTATION) this.box.orientation = Clutter.Orientation.HORIZONTAL; // GNOME 48 else this.box.vertical = false; // GNOME 46/47 this.actor.add_child(this.box); // Submenu Container this.sub = HAS_ST_ORIENTATION ? new St.BoxLayout({ clip_to_allocation: true, orientation: Clutter.Orientation.VERTICAL, // GNOME 48 x_expand: true, }) : new St.BoxLayout({ clip_to_allocation: true, vertical: true, // GNOME 46/47 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-ea89821/src/shell/input.js��������������������������������0000664�0000000�0000000�00000002124�14771776374�0025524�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-ea89821/src/shell/keybindings.js��������������������������0000664�0000000�0000000�00000006242�14771776374�0026700�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 * @returns {*} 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-ea89821/src/shell/notification.js�������������������������0000664�0000000�0000000�00000036324�14771776374�0027064�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 NotificationDaemon from 'resource:///org/gnome/shell/ui/notificationDaemon.js'; import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; import {HAS_MESSAGELIST_NOTIFICATIONMESSAGE, getIcon} from './utils.js'; const {NotificationMessage} = HAS_MESSAGELIST_NOTIFICATIONMESSAGE ? await import('resource:///org/gnome/shell/ui/messageList.js') // GNOME 48 : await import('resource:///org/gnome/shell/ui/calendar.js'); // GNOME 46/47 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 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 { // 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 { // 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; const title = notification.title; const body = notification.body ? notification.body : null; // Bail early If @notification represents an exact repeat if (cachedNotification.title === title && cachedNotification.body === body) return cachedNotification; // If the details have changed, flag as an update cachedNotification.title = title; cachedNotification.body = body; cachedNotification.acknowledged = false; 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)) 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; }; /** * Update the prototype for {@link GtkNotificationDaemon}. */ export function patchGtkNotificationDaemon() { GtkNotificationDaemon.prototype._ensureAppSource = _ensureAppSource; } /** * Restore the prototype for {@link GtkNotificationDaemon}. */ 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; /** * Update the prototype for {@link NotificationDaemon.GtkNotificationDaemonAppSource}. */ 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 { // If we fail, reset in case we can try again notification._remoteWithdrawn = false; } } ); }; NotificationDaemon.GtkNotificationDaemonAppSource.prototype._withdrawGSConnectNotification = _withdrawGSConnectNotification; } /** * Restore the prototype for {@link NotificationDaemon.GtkNotificationDaemonAppSource}. */ export function unpatchGtkNotificationSources() { NotificationDaemon.GtkNotificationDaemonAppSource.prototype.addNotification = _addNotification; delete NotificationDaemon.GtkNotificationDaemonAppSource.prototype._withdrawGSConnectNotification; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/src/shell/tooltip.js������������������������������0000664�0000000�0000000�00000020375�14771776374�0026067�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'; import {HAS_ST_ORIENTATION} from './utils.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 { if (HAS_ST_ORIENTATION) { // GNOME 48 this._bin.child = new St.BoxLayout( {orientation: Clutter.Orientation.HORIZONTAL} ); } else { // GNOME 46/47 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.addTopChrome(this._bin); } 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.removeChrome(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.removeChrome(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-ea89821/src/shell/utils.js��������������������������������0000664�0000000�0000000�00000004207�14771776374�0025531�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 Gtk from 'gi://Gtk'; import St from 'gi://St'; import {PACKAGE_VERSION} from 'resource:///org/gnome/shell/misc/config.js'; export const SHELL_MAJOR_VERSION = Number(PACKAGE_VERSION.split('.')[0]); export const HAS_ST_ORIENTATION = SHELL_MAJOR_VERSION >= 48; export const HAS_MESSAGELIST_NOTIFICATIONMESSAGE = SHELL_MAJOR_VERSION >= 48; /** * Get a themed icon, using fallbacks from GSConnect's GResource when necessary. * * @param {string} name - A themed icon name * @returns {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}); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/src/stylesheet.css��������������������������������0000664�0000000�0000000�00000004651�14771776374�0025632�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-ea89821/src/utils/����������������������������������������0000775�0000000�0000000�00000000000�14771776374�0024061�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/src/utils/remote.js�������������������������������0000664�0000000�0000000�00000034510�14771776374�0025715�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', }; /** * Initialize a Gio.DBusProxy in an awaitable manner * * @param {Gio.DBusProxy} proxy - The proxy object to initialize * @param {Gio.Cancellable} [cancellable] - An optional cancellable object * @returns {Promise} An awaitable Promise */ 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 { 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-ea89821/src/utils/setup.js��������������������������������0000664�0000000�0000000�00000021735�14771776374�0025567�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); } /** * Get the contents of a GResource file, replacing `@PACKAGE_DATADIR@` where * necessary. * * @param {string} relativePath - A path relative to GSConnect's resource path * @returns {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 * @returns {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 * @returns {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])); } } /** * Initialise and setup Config, GResources and GSchema. * * @param {string} extensionPath - The absolute path to the extension directory */ export 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-ea89821/src/wl_clipboard.js�������������������������������0000664�0000000�0000000�00000020705�14771776374�0025724�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 { 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 * * @returns {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 * * @returns {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 * @returns {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-ea89821/webextension/�������������������������������������0000775�0000000�0000000�00000000000�14771776374�0024644�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/.editorconfig������������������������0000664�0000000�0000000�00000000457�14771776374�0027327�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-ea89821/webextension/_locales/����������������������������0000775�0000000�0000000�00000000000�14771776374�0026425�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/ar/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027027�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/ar/messages.json������������0000664�0000000�0000000�00000003251�14771776374�0031532�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-ea89821/webextension/_locales/ar/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033153�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-ea89821/webextension/_locales/be/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027013�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/be/messages.json������������0000664�0000000�0000000�00000003232�14771776374�0031515�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-ea89821/webextension/_locales/be/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033137�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-ea89821/webextension/_locales/ca/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027010�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/ca/messages.json������������0000664�0000000�0000000�00000003071�14771776374�0031513�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-ea89821/webextension/_locales/ca/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033134�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-ea89821/webextension/_locales/cs/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027032�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/cs/messages.json������������0000664�0000000�0000000�00000003101�14771776374�0031527�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-ea89821/webextension/_locales/cs/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033156�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-ea89821/webextension/_locales/da/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027011�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/da/messages.json������������0000664�0000000�0000000�00000003015�14771776374�0031512�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-ea89821/webextension/_locales/da/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033135�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-ea89821/webextension/_locales/de/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027015�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/de/messages.json������������0000664�0000000�0000000�00000003025�14771776374�0031517�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-ea89821/webextension/_locales/de/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033141�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-ea89821/webextension/_locales/el/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027025�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/el/messages.json������������0000664�0000000�0000000�00000003215�14771776374�0031530�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-ea89821/webextension/_locales/el/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033151�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-ea89821/webextension/_locales/en/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027027�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/en/messages.json������������0000664�0000000�0000000�00000002777�14771776374�0031546�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-ea89821/webextension/_locales/en/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033153�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-ea89821/webextension/_locales/es/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027034�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/es/messages.json������������0000664�0000000�0000000�00000003065�14771776374�0031542�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-ea89821/webextension/_locales/es/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033160�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-ea89821/webextension/_locales/et/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027035�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/et/messages.json������������0000664�0000000�0000000�00000002775�14771776374�0031552�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-ea89821/webextension/_locales/et/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033161�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-ea89821/webextension/_locales/fa-IR/����������������������0000775�0000000�0000000�00000000000�14771776374�0027323�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/fa-IR/messages.json���������0000664�0000000�0000000�00000003254�14771776374�0032031�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-ea89821/webextension/_locales/fa-IR/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033447�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-ea89821/webextension/_locales/fa/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027013�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/fa/messages.json������������0000664�0000000�0000000�00000003254�14771776374�0031521�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-ea89821/webextension/_locales/fa/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033137�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-ea89821/webextension/_locales/fi/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027023�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/fi/messages.json������������0000664�0000000�0000000�00000003064�14771776374�0031530�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-ea89821/webextension/_locales/fi/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033147�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-ea89821/webextension/_locales/fr/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027034�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/fr/messages.json������������0000664�0000000�0000000�00000003061�14771776374�0031536�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-ea89821/webextension/_locales/fr/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033160�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-ea89821/webextension/_locales/fy-NL/����������������������0000775�0000000�0000000�00000000000�14771776374�0027352�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/fy-NL/messages.json���������0000664�0000000�0000000�00000003040�14771776374�0032051�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-ea89821/webextension/_locales/fy-NL/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033476�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-ea89821/webextension/_locales/gl/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027027�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/gl/messages.json������������0000664�0000000�0000000�00000003047�14771776374�0031535�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-ea89821/webextension/_locales/gl/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033153�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-ea89821/webextension/_locales/hu/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027041�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/hu/messages.json������������0000664�0000000�0000000�00000003076�14771776374�0031551�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-ea89821/webextension/_locales/hu/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033165�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-ea89821/webextension/_locales/id-ID/����������������������0000775�0000000�0000000�00000000000�14771776374�0027313�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/id-ID/messages.json���������0000664�0000000�0000000�00000003026�14771776374�0032016�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-ea89821/webextension/_locales/id-ID/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033437�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-ea89821/webextension/_locales/it/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027041�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/it/messages.json������������0000664�0000000�0000000�00000003041�14771776374�0031541�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-ea89821/webextension/_locales/it/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033165�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-ea89821/webextension/_locales/ko-KR/����������������������0000775�0000000�0000000�00000000000�14771776374�0027350�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/ko-KR/messages.json���������0000664�0000000�0000000�00000003124�14771776374�0032052�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-ea89821/webextension/_locales/ko-KR/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033474�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-ea89821/webextension/_locales/ko/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027036�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/ko/messages.json������������0000664�0000000�0000000�00000003057�14771776374�0031545�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-ea89821/webextension/_locales/ko/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033162�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-ea89821/webextension/_locales/lt/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027044�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/lt/messages.json������������0000664�0000000�0000000�00000003070�14771776374�0031546�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-ea89821/webextension/_locales/lt/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033170�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-ea89821/webextension/_locales/nl-BE/����������������������0000775�0000000�0000000�00000000000�14771776374�0027322�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/nl-BE/messages.json���������0000664�0000000�0000000�00000003004�14771776374�0032021�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-ea89821/webextension/_locales/nl-BE/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033446�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-ea89821/webextension/_locales/nl-NL/����������������������0000775�0000000�0000000�00000000000�14771776374�0027345�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/nl-NL/messages.json���������0000664�0000000�0000000�00000003032�14771776374�0032045�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-ea89821/webextension/_locales/nl-NL/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033471�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-ea89821/webextension/_locales/pl/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027040�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/pl/messages.json������������0000664�0000000�0000000�00000003136�14771776374�0031545�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-ea89821/webextension/_locales/pl/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033164�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-ea89821/webextension/_locales/pt-BR/����������������������0000775�0000000�0000000�00000000000�14771776374�0027351�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/pt-BR/messages.json���������0000664�0000000�0000000�00000003051�14771776374�0032052�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-ea89821/webextension/_locales/pt-BR/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033475�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-ea89821/webextension/_locales/pt-PT/����������������������0000775�0000000�0000000�00000000000�14771776374�0027371�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/pt-PT/messages.json���������0000664�0000000�0000000�00000003060�14771776374�0032072�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-ea89821/webextension/_locales/pt-PT/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033515�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-ea89821/webextension/_locales/ru/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027053�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/ru/messages.json������������0000664�0000000�0000000�00000003160�14771776374�0031555�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-ea89821/webextension/_locales/ru/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033177�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-ea89821/webextension/_locales/sk/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027042�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/sk/messages.json������������0000664�0000000�0000000�00000003076�14771776374�0031552�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-ea89821/webextension/_locales/sk/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033166�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-ea89821/webextension/_locales/sr-Latn/��������������������0000775�0000000�0000000�00000000000�14771776374�0027745�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/sr-Latn/messages.json�������0000664�0000000�0000000�00000003025�14771776374�0032447�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�14771776374�0034012�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�GSConnect-gnome-shell-extension-gsconnect-ea89821/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-ea89821/webextension/_locales/sr/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027051�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/sr/messages.json������������0000664�0000000�0000000�00000003216�14771776374�0031555�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-ea89821/webextension/_locales/sr/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033175�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-ea89821/webextension/_locales/sv-SE/����������������������0000775�0000000�0000000�00000000000�14771776374�0027362�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/sv-SE/messages.json���������0000664�0000000�0000000�00000003040�14771776374�0032061�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-ea89821/webextension/_locales/sv-SE/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033506�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-ea89821/webextension/_locales/sv/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027055�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/sv/messages.json������������0000664�0000000�0000000�00000002777�14771776374�0031574�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-ea89821/webextension/_locales/sv/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033201�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-ea89821/webextension/_locales/tr/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027052�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/tr/messages.json������������0000664�0000000�0000000�00000003021�14771776374�0031550�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-ea89821/webextension/_locales/tr/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033176�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-ea89821/webextension/_locales/uk/�������������������������0000775�0000000�0000000�00000000000�14771776374�0027044�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/uk/messages.json������������0000664�0000000�0000000�00000003277�14771776374�0031557�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-ea89821/webextension/_locales/uk/messages.json.license����0000664�0000000�0000000�00000000164�14771776374�0033170�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-ea89821/webextension/_locales/zh-CN/����������������������0000775�0000000�0000000�00000000000�14771776374�0027344�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/zh-CN/messages.json���������0000664�0000000�0000000�00000003012�14771776374�0032042�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-ea89821/webextension/_locales/zh-CN/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033470�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-ea89821/webextension/_locales/zh-TW/����������������������0000775�0000000�0000000�00000000000�14771776374�0027376�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/_locales/zh-TW/messages.json���������0000664�0000000�0000000�00000002776�14771776374�0032114�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-ea89821/webextension/_locales/zh-TW/messages.json.license�0000664�0000000�0000000�00000000164�14771776374�0033522�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-ea89821/webextension/background.html����������������������0000664�0000000�0000000�00000000563�14771776374�0027655�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-ea89821/webextension/gettext.js���������������������������0000775�0000000�0000000�00000007100�14771776374�0026667�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'; 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-ea89821/webextension/images/������������������������������0000775�0000000�0000000�00000000000�14771776374�0026111�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/desktop.svg�������������������0000664�0000000�0000000�00000000422�14771776374�0030301�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-ea89821/webextension/images/gsconnect-128.png�������������0000664�0000000�0000000�00000002500�14771776374�0031107�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������>a���sBIT|d��� pHYs��;��;̶���tEXtSoftware�www.inkscape.org<��IDATx=oEgf;Cp!++J,h4H4A`޿BJ$y))\C˅-ErAPĖE zvgvU9{_s{�F5hWt/� `Hd6J=LJ[|b-!J}6QΟ\@nd�wا0ݰݠI�˶4 �EH~-5|tТ,srhR |1В,hВ, 0�Ȳ Jy=�8gҞC�R$T?p/Ob�S�GT$U?SoO䁘TqNNY]f*7*`۠>2#U1޻y ;:_) [][Z!pAwN3O̴d 0�04|Iկzؿ]A? ]Z>=Z˴³S @!^{n7�4U?ҰY B4Ȑfd�,xo`�,vok �XIaMb~�,('1� I b~ت�cڳ�X$`X�kb~0EOb�I W?) DOb�*R~PB�T?R~p.'T$�U?@zOb�^@OJ>�)V?LJOJ�'1�IMp| IM| I(C'Y?x<>Dè0Sr~#姛J᣶XOZo'-�.~;|U?in�UW.O8}>T �}T;>I'_qAr''`Jl$!T?A =̒41Fi?ltmw5Xb0ƌ�T1EfꙈRo/Qy*(gU=�xV׮�X=wwV OFGFIcK612L>Pzzc8"_~'c(\QpV1 ~Vz}?}BDDDDDDDDDDDDDDDDDDDDA>����IENDB`������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/gsconnect-16.png��������������0000664�0000000�0000000�00000000641�14771776374�0031027�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������a���sBIT|d��� pHYs��b��b8z���tEXtSoftware�www.inkscape.org<��IDAT8Ő1KBa*A"!mlh PZ74Қw) )47$9 vE!z93A~S ˷w 3 g_re�LUT>NDD[#n_n:~/!˙D+j:@U[pǃ kpf0Ԫ>?S<gލ mTZ /&yiFH9{ө%h<B6N<W eI.3fx$]>m?a&J̧����IENDB`�����������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/gsconnect-22.png��������������0000664�0000000�0000000�00000001101�14771776374�0031014�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������Ĵl;���sBIT|d��� pHYs�� &�� &Q3���tEXtSoftware�www.inkscape.org<��IDAT8ݔ1O1%Q:B-H"X0? CF$:e$Xʀtr@/ MrJw0;ٯǟ_(JSDǕJ� �O90z3DJ5lo}NZ#%33k-+4.QbD$຿8.5Z]TTRJl~}Q* 坵H0ټ ]fxG?pzWHe \`f뫌޳H&OoHT`2Vb)0aThϻD`4_^U4 ثVjߗ(N@Ag0�ll2i"*o{y�qI}'*ǹhY�siw]:G7cLŅ7l>:Bm}z�s ����IENDB`���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/gsconnect-24.png��������������0000664�0000000�0000000�00000001125�14771776374�0031024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������w=���sBIT|d��� pHYs�� �� ����tEXtSoftware�www.inkscape.org<��IDATH=oP{c;JRBH-R "X:PFfo/�R"!13Bi,H($6 Ƥag9}>[#>yv@%szFc!H)q^3pq4`^+ D ln1p8rCJBMDFqYXl { hF'5 Jgۯ~p^)l " BQfNOfC }!R5]i BRbA&(ZniijJfυ�*dgAf?D K6ٙm <?]p~Uٶz*(XV%>a(&GsL\6cARQ=nZJv &�ZK!b%M? nvA-N AlP۟8^XvV \����IENDB`�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/gsconnect-256.png�������������0000664�0000000�0000000�00000005304�14771776374�0031116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������\rf���sBIT|d��� pHYs��v��vx���tEXtSoftware�www.inkscape.org<�� AIDATx_hwybҤmou"sv*EE/-n"" :00Y\fk?S*d!,solsIٞ_4*�����������������������������������������������,.<yfus<$"C]Y+f:rȋ/%"Y օ 2�SSS#G.JD@i}!ą8䙉'f;97_}^M7C\hx1 )} )NFDp$5&:h1�h1�h1�h1�h1�h1�h`g>"e }>B_@<_c�$Y^1� Iy>T_h< �|y. �3*� )YmUx&_�$W=� U@2Hc�ҟ �@@H"z?�Db�5ҟ_ �E@H1�/ aDa�_8 �C @THa1� /,� dzhc�_=�DWs! ewdkrsmM?|zML*U)+ ,w}y>ڕI;;;2+tMz^%ᰈN8~g˧>\pRP;+s\/B=#Fe˯ʭ:QGǎ}2e�P++G:M�ԦL[ZE9A �jS&\0N[`�P2omm-IAd@Ԣ>1:OOWu e?sOGk�SÇ?G �S<s2: OF|4@P|DH@P|vh8< $1�v$@0Lݖ �!B?@?[ ҟ-T�xGL�HOc�2i �"B?W?[1�g#i:g#i � J�/Hҟ� ҟ1�Vg?g3i �*E՝4^)Tg;i �*CŐ4�!bHʐl1?@%HXҟi,ҟ-1�80ҟ-Hҟ�@Hҟ�@Hҟ�@Hҟ�`Hӟ=ҟ-1�ҟ- b_HXӟ�``?[Oc�00ҟ-1�s� g=ii bOHҟƫHҟ�ҟ-1�(gK%i �J!RI�ϖR;1#RJHҟ+Hҟ�`O?[Oc�'ҟ-1�ϖb�ϖj>=!RM{l)?W RN]Hӟ�`ҟ-1�؅gK=i �gkBӚH&?hJx!"24�4�4�4H?_J#ٚ迲_|#Ś4�}41i �?�1�4�&?_!MN�?-�lP?-�|@%ڐ@%ڐ4fD:@ڐ4tEdujKӂ|[DjkՖ͍'/H75A)i,,,l_әN:q!"mJ<?O.:'?y 4q5ږ4o�?�p#%mKZ͏1i~HMӟV�p#5mMZ%͏5i�n~O;�p#UmNھ)ks5�HYӟ6͏Ե=i �7?RGۭ�p H�n~4^�p)H2rƍgBw2X~ 3Hn8p)A�_H{3!Bۛ9�NgY\q=a+SW<J @)"T_g'wu{<0+:_V:^^u|CD>x� .Ϳ."<@`jTgn伈x8*"<�/G:�}:bvjgn㗅TgϞb_s="NNȡ*v d7}=ίH!j>#u#Eo> |PR~�DIXRUa=@ ^HP7so1�{חtH_ygzm}~jH[~|eߔ譯wf?328k/]'RO̿@DR&&'qDiyHH"D3e����������������������������������������������������������VaaS(u����IENDB`����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/gsconnect-32.png��������������0000664�0000000�0000000�00000000773�14771776374�0031033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR��� ��� ���szz���sBIT|d��� pHYs����+���tEXtSoftware�www.inkscape.org<��xIDATX1N@DZ�HDB QpD"q� :(q\J !hmMycx2Cp?`>^pf.Mqjr`fhd_7BN9Xˊ�Da�"*5ړq+�F`Tb^5 whhX>_rr)A<v„Po\|z R=P/\WS0$bhe*`%n̟(z&9`߶? 2}ȯ W>Mg� #@~2b'�Nt3:Snwx<uzE/Y21D����IENDB`�����GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/gsconnect-48.png��������������0000664�0000000�0000000�00000001417�14771776374�0031036�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���0���0���W���sBIT|d��� pHYs��%��%IR$���tEXtSoftware�www.inkscape.org<��IDAThkA즩=yՋ1-=H-=x?c!C?[0zH{5kSk/lM~~3 $h-LMߖ tcWNJ([g O<b)cFOxY&& LLSH)3X`q R]gؓD !PREF|r0 6}HЀR !")'^;jA_�ެ&b@J2!#ҨR "Y?#W()YYH Y[d1n輒, ?}ےؖT>{ ٝW͘ Kαc10bӋ!N/#:n nVY[]- (?~N/m+iQQ ^/*Uw$:\B7ȠQZo`HAӋ!G'@.7t [h{@ )&:ַiwӧn Zdp GC='iX,>j ꃭ{{/q7ZW =_)$j�cDir]UntLRRRRp(z����IENDB`�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/gsconnect-64.png��������������0000664�0000000�0000000�00000001417�14771776374�0031034�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���@���@���iq���sBIT|d��� pHYs����e���tEXtSoftware�www.inkscape.org<��IDATx?0ؘ(7#!!1f@bDT [K%֛ Ԯ !tfFoc;T_H$D"qT!]Õ/AiŒb<~YhxH>x� ak tYG-NcoߺSfFUhF#@ʬ"͉H" G$27 BnSkEM _lm?g T|x v_/zU ^<�ׇk3S'?Azx `{W9XO F�[wU>@)VNe�[n.!Ee�7 3<>8A }>N0|Fy>� B"oqZFqZF- a㴾lnqfͺ AIqV{q:M48Kb<`}R@lo}L&7WH+ƍyIq*Dz?~~�ʎxZ\[uac{'ޙ^~wh/@`tph:J$D"HD/Y4����IENDB`�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/images/gsconnect.svg�����������������0000664�0000000�0000000�00000004462�14771776374�0030623�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-ea89821/webextension/images/laptop.svg��������������������0000664�0000000�0000000�00000000671�14771776374�0030135�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-ea89821/webextension/images/message.svg�������������������0000664�0000000�0000000�00000000432�14771776374�0030255�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-ea89821/webextension/images/open-in-browser.svg�����������0000664�0000000�0000000�00000000441�14771776374�0031657�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-ea89821/webextension/images/phone.svg���������������������0000664�0000000�0000000�00000000443�14771776374�0027744�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-ea89821/webextension/images/tablet.svg��������������������0000664�0000000�0000000�00000000721�14771776374�0030105�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-ea89821/webextension/js/����������������������������������0000775�0000000�0000000�00000000000�14771776374�0025260�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/js/background.js���������������������0000664�0000000�0000000�00000026267�14771776374�0027752�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 * * @param {Error} error - A caught exception */ function logError(error) { if (!_MUTE.includes(error.message)) console.error(error.message); } /** * Callback for activation of the extension toolbar icon * * @param {browser.tabs.Tab} tab - the current tab */ 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 { browser.browserAction.disable(); } } /** * Send a message to the native-messaging-host * * @param {object} message - The message to forward */ 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 */ async function onPopupMessage(message, sender) { 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 {browser.menus.OnClickData} info - Information about the item and context */ async function onContextItem(info) { 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 {browser.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 */ async function onDisconnect() { 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-ea89821/webextension/js/browser-polyfill.min.js�����������0000664�0000000�0000000�00000023722�14771776374�0031721�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-ea89821/webextension/js/browser-polyfill.min.js.map�������0000664�0000000�0000000�00000151430�14771776374�0032473�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�14771776374�0034026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/js������������������������������������������������������������������������������������������������������SPDX-FileCopyrightText: Mozilla Foundation SPDX-License-Identifier: MPL-2.0 ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������GSConnect-gnome-shell-extension-gsconnect-ea89821/webextension/js/popup.js��������������������������0000664�0000000�0000000�00000011726�14771776374�0026770�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 * * @param {Error} error - A caught exception */ 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 * @returns {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 {browser.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-ea89821/webextension/manifest.chrome.json�����������������0000664�0000000�0000000�00000002166�14771776374�0030626�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-ea89821/webextension/manifest.chrome.json.license���������0000664�0000000�0000000�00000000164�14771776374�0032243�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-ea89821/webextension/manifest.firefox.json����������������0000664�0000000�0000000�00000001613�14771776374�0031007�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-ea89821/webextension/manifest.firefox.json.license��������0000664�0000000�0000000�00000000164�14771776374�0032430�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-ea89821/webextension/mkwebext.sh��������������������������0000775�0000000�0000000�00000004361�14771776374�0027035�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-ea89821/webextension/popup.html���������������������������0000664�0000000�0000000�00000000670�14771776374�0026700�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-ea89821/webextension/stylesheet.css�����������������������0000664�0000000�0000000�00000001566�14771776374�0027557�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; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������